涉及知识点: offsetLeft, offsetTop, offsetWidth, offsetHeight;offsetX, offsetY;clientX,clientY
css:clip-path
学习直通车:HTMLElement.offsetLeft - Web API 接口参考 | MDN
MouseEvent.offsetX - Web API 接口参考 | MDN
MouseEvent.clientX - Web API 接口参考 | MDN
1.布局搭建
<template>
<div class="wrap">
<div ref="box1" class="box1" @mousemove="onMouseMove" @mouseup="onMouseUp" @mouseleave="onMouseUp">
<img class="img1" src="./images/7.jpg" alt="" />
<img ref="clipImg" class="img2" src="./images/7.jpg" alt="" />
<div ref="cropBox" class="cropBox" @mousedown="onMouseCropBoxDown">
<div class="leftUp" @mousedown="onMouseDownDot($event, 'leftUp')"></div>
<div class="up" @mousedown="onMouseDownDot($event, 'up')"></div>
<div class="rightUp" @mousedown="onMouseDownDot($event, 'rightUp')"></div>
<div class="right" @mousedown="onMouseDownDot($event, 'right')"></div>
<div class="rightDown" @mousedown="onMouseDownDot($event, 'rightDown')"></div>
<div class="down" @mousedown="onMouseDownDot($event, 'down')"></div>
<div class="leftDown" @mousedown="onMouseDownDot($event, 'leftDown')"></div>
<div class="left" @mousedown="onMouseDownDot($event, 'left')"></div>
</div>
</div>
<div class="box2">
<img ref="previewImg" src="./images/7.jpg" alt="" />
</div>
</div>
</template>
2.css样式
@mixin css混入
$boxWidth 变量定义
<style lang="scss">
.wrap {
display: flex;
padding: 30px;
$boxWidth: 400px; //盒子大小
$boxHeight: 400px;
$defaultCropBoxWidth: 150px; //裁切框初始值
$defaultCropBoxHeight: 150px;
$dotWidth: 6px; //点
.box1 {
width: $boxWidth;
height: $boxHeight;
border: 1px solid #000;
position: relative;
box-sizing: border-box;
@mixin img {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
-webkit-user-drag: none !important;
user-select: none;
}
.img1 {
@include img;
opacity: 0.5;
}
.img2 {
@include img;
clip-path: polygon(
0 0,
$defaultCropBoxWidth 0,
$defaultCropBoxWidth $defaultCropBoxHeight,
0 $defaultCropBoxHeight
);
}
.cropBox {
position: absolute;
left: 0;
top: 0;
width: $defaultCropBoxWidth;
height: $defaultCropBoxHeight;
border: 1px solid #fff;
cursor: all-scroll;
$halfDotWidth: calc(6px / -2);
@mixin dot {
position: absolute;
width: $dotWidth;
height: $dotWidth;
background-color: #fff;
}
.leftUp {
@include dot;
top: $halfDotWidth;
left: $halfDotWidth;
cursor: nw-resize;
}
.up {
@include dot;
top: $halfDotWidth;
left: 50%;
margin-left: $halfDotWidth;
cursor: n-resize;
}
.rightUp {
@include dot;
top: $halfDotWidth;
right: $halfDotWidth;
cursor: ne-resize;
}
.right {
@include dot;
top: 50%;
margin-top: $halfDotWidth;
right: $halfDotWidth;
cursor: e-resize;
}
.rightDown {
@include dot;
bottom: $halfDotWidth;
right: $halfDotWidth;
cursor: se-resize;
}
.down {
@include dot;
bottom: $halfDotWidth;
left: 50%;
margin-left: $halfDotWidth;
cursor: s-resize;
}
.leftDown {
@include dot;
bottom: $halfDotWidth;
left: $halfDotWidth;
cursor: sw-resize;
}
.left {
@include dot;
top: 50%;
margin-top: $halfDotWidth;
left: $halfDotWidth;
cursor: w-resize;
}
}
}
.box2 {
width: $boxWidth;
height: $boxHeight;
margin-left: 50px;
position: relative;
img {
position: absolute;
left: 0;
top: 0;
width: 400px;
height: 400px;
display: block;
clip-path: polygon(
0 0,
$defaultCropBoxWidth 0,
$defaultCropBoxWidth $defaultCropBoxHeight,
0 $defaultCropBoxHeight
);
-webkit-user-drag: none !important;
user-select: none;
}
}
}
</style>
以上可以完成如下布局
3.实现拖拽预览
<script>
const WIDTH = 400;
const HEIGHT = 400;
const MIN = 50;
export default {
data() {
return {
startCropMouseX: 0,
stertCropMouseY: 0,
inSelectBox: false, //判断是否在选框中拖动
};
},
mounted() {},
methods: {
// 选框移动
onMouseMove(e) {
// offsetLeft返回当前元素左上角相对于 HTMLElement.offsetParent 节点的左边界偏移的像素值。
const { offsetLeft, offsetTop, offsetWidth, offsetHeight } = this.$refs.cropBox;
const { offsetX, offsetY } = e;
if (this.inSelectBox) {
const xChange = offsetX - this.startCropMouseX; //获取移动距离
const yChange = offsetY - this.stertCropMouseY;
let left = offsetLeft + xChange; //每次移动时,获取选框位置
let top = offsetTop + yChange;
if (left <= 0) {
left = 0;
this.$refs.cropBox.style.left = `${left}px`;
} else if (left + offsetWidth >= WIDTH) {
left = WIDTH - offsetWidth; //总宽度-自身宽度
this.$refs.cropBox.style.left = `${left}px`;
} else {
this.$refs.cropBox.style.left = `${left}px`;
}
if (top <= 0) {
top = 0;
this.$refs.cropBox.style.top = `${top}px`;
} else if (top + offsetHeight >= HEIGHT) {
top = HEIGHT - offsetHeight; //总高度-自身高度
this.$refs.cropBox.style.top = `${top}px`;
} else {
this.$refs.cropBox.style.top = `${top}px`;
}
this.setHeightView();
this.setPreviewImg();
}
},
// 鼠标抬起
onMouseUp(e) {
this.inSelectBox = false;
},
// 选框拖动
onMouseCropBoxDown(e) {
const { offsetX, offsetY } = e;
this.startCropMouseX = offsetX;
this.stertCropMouseY = offsetY;
this.inSelectBox = true;
},
// 设置选区可见位置
setHeightView() {
const { offsetLeft, offsetTop, offsetHeight, offsetWidth } = this.$refs.cropBox;
this.$refs.clipImg.style.clipPath = this.getLabel(offsetLeft, offsetTop, offsetHeight, offsetWidth);
},
// 设置预览图片
setPreviewImg() {
const { offsetLeft, offsetTop, offsetHeight, offsetWidth } = this.$refs.cropBox;
this.$refs.previewImg.style.left = `-${offsetLeft}px`;
this.$refs.previewImg.style.top = `-${offsetTop}px`;
this.$refs.previewImg.style.clipPath = this.getLabel(offsetLeft, offsetTop, offsetHeight, offsetWidth);
},
// 获取裁切path
getLabel(offsetLeft, offsetTop, offsetHeight, offsetWidth) {
return `polygon(${offsetLeft}px ${offsetTop}px,
${offsetLeft + offsetWidth}px ${offsetTop}px,
${offsetLeft + offsetWidth}px ${offsetTop + offsetHeight}px,
${offsetLeft}px ${offsetTop + offsetHeight}px)`;
}
}
};
</script>
4.实现裁剪
实现裁剪主要是进行点的拖拽,主要方法是上下左右;对于上左可以采用上的方法+左的方法即可
定义变量:
currentDot: '',
isDotDown: false //是否点位拉伸
上 下 左 右 方法
// 右移 获取拉伸宽度+cropBox自身宽度
rightMove(e) {
let x = e.clientX; //鼠标X坐标(用offsetX有bug)
const { offsetLeft } = this.$refs.box1;
const maxX = offsetLeft + WIDTH - 2;
if (x > maxX) {
x = maxX;
}
const { offsetWidth, offsetLeft: left } = this.$refs.cropBox;
// 上一次点到浏览器距离
const preX = offsetLeft + left + offsetWidth;
const addWidth = x - preX;
this.$refs.cropBox.style.width = `${offsetWidth + addWidth}px`;
},
// 上移
upMove(e) {
let y = e.clientY; //鼠标Y坐标
const { offsetTop } = this.$refs.box1;
if (y < offsetTop) {
y = offsetTop;
}
const { offsetHeight, offsetTop: top } = this.$refs.cropBox;
// 右侧线距离浏览器的距离-y
this.$refs.cropBox.style.height = `${offsetHeight + offsetTop + top - y}px`;
// y-parent距离浏览器的距离
this.$refs.cropBox.style.top = `${y - offsetTop}px`;
},
// 下移
downMove(e) {
let y = e.clientY;
const { offsetTop } = this.$refs.box1;
const maxY = offsetTop + HEIGHT - 2;
if (y > maxY) {
y = maxY;
}
const { offsetHeight, offsetTop: top } = this.$refs.cropBox;
// 上一次点到浏览器距离
const preY = offsetHeight + top + offsetTop;
const addWidth = y - preY;
this.$refs.cropBox.style.height = `${offsetHeight + addWidth}px`;
},
// 左移
leftMove(e) {
let x = e.clientX; //鼠标X坐标
const { offsetLeft } = this.$refs.box1;
if (x < offsetLeft) {
x = offsetLeft;
}
const { offsetWidth, offsetLeft: left } = this.$refs.cropBox;
// 右侧线距离浏览器的距离-x
this.$refs.cropBox.style.width = `${offsetWidth + offsetLeft + left - x}px`;
// x-parent距离浏览器的距离
this.$refs.cropBox.style.left = `${x - offsetLeft}px`;
}
给每个点增加方法调用
// 选框移动
onMouseMove(e) {
.....之前代码
if (this.isDotDown) {
switch (this.currentDot) {
case 'right':
this.rightMove(e);
break;
case 'up':
this.upMove(e);
break;
case 'left':
this.leftMove(e);
break;
case 'down':
this.downMove(e);
break;
case 'rightUp':
this.rightMove(e);
this.upMove(e);
break;
case 'leftUp':
this.leftMove(e);
this.upMove(e);
break;
case 'leftDown':
this.leftMove(e);
this.downMove(e);
break;
case 'rightDown':
this.rightMove(e);
this.downMove(e);
break;
default:
break;
}
this.setHeightView();
this.setPreviewImg();
}
},
完整的代码文件查看:
<template>
<div class="wrap">
<div ref="box1" class="box1" @mousemove="onMouseMove" @mouseup="onMouseUp" @mouseleave="onMouseUp">
<img class="img1" src="./images/7.jpg" alt="" />
<img ref="clipImg" class="img2" src="./images/7.jpg" alt="" />
<div ref="cropBox" class="cropBox" @mousedown="onMouseCropBoxDown">
<div class="leftUp" @mousedown="onMouseDownDot($event, 'leftUp')"></div>
<div class="up" @mousedown="onMouseDownDot($event, 'up')"></div>
<div class="rightUp" @mousedown="onMouseDownDot($event, 'rightUp')"></div>
<div class="right" @mousedown="onMouseDownDot($event, 'right')"></div>
<div class="rightDown" @mousedown="onMouseDownDot($event, 'rightDown')"></div>
<div class="down" @mousedown="onMouseDownDot($event, 'down')"></div>
<div class="leftDown" @mousedown="onMouseDownDot($event, 'leftDown')"></div>
<div class="left" @mousedown="onMouseDownDot($event, 'left')"></div>
</div>
</div>
<div class="box2">
<img ref="previewImg" src="./images/7.jpg" alt="" />
</div>
</div>
</template>
<script>
const WIDTH = 400;
const HEIGHT = 400;
const MIN = 50;
export default {
data() {
return {
currentDot: '',
startCropMouseX: 0,
stertCropMouseY: 0,
inSelectBox: false, //判断是否在选框中拖动
isDotDown: false //是否是点位拉伸
};
},
mounted() {},
methods: {
// 选框移动
onMouseMove(e) {
// offsetLeft返回当前元素左上角相对于 HTMLElement.offsetParent 节点的左边界偏移的像素值。
const { offsetLeft, offsetTop, offsetWidth, offsetHeight } = this.$refs.cropBox;
const { offsetX, offsetY } = e;
if (this.inSelectBox) {
const xChange = offsetX - this.startCropMouseX; //获取移动距离
const yChange = offsetY - this.stertCropMouseY;
let left = offsetLeft + xChange; //每次移动时,获取选框位置
let top = offsetTop + yChange;
if (left <= 0) {
left = 0;
this.$refs.cropBox.style.left = `${left}px`;
} else if (left + offsetWidth >= WIDTH) {
left = WIDTH - offsetWidth; //总宽度-自身宽度
this.$refs.cropBox.style.left = `${left}px`;
} else {
this.$refs.cropBox.style.left = `${left}px`;
}
if (top <= 0) {
top = 0;
this.$refs.cropBox.style.top = `${top}px`;
} else if (top + offsetHeight >= HEIGHT) {
top = HEIGHT - offsetHeight; //总高度-自身高度
this.$refs.cropBox.style.top = `${top}px`;
} else {
this.$refs.cropBox.style.top = `${top}px`;
}
this.setHeightView();
this.setPreviewImg();
}
if (this.isDotDown) {
switch (this.currentDot) {
case 'right':
this.rightMove(e);
break;
case 'up':
this.upMove(e);
break;
case 'left':
this.leftMove(e);
break;
case 'down':
this.downMove(e);
break;
case 'rightUp':
this.rightMove(e);
this.upMove(e);
break;
case 'leftUp':
this.leftMove(e);
this.upMove(e);
break;
case 'leftDown':
this.leftMove(e);
this.downMove(e);
break;
case 'rightDown':
this.rightMove(e);
this.downMove(e);
break;
default:
break;
}
this.setHeightView();
this.setPreviewImg();
}
},
// 鼠标抬起
onMouseUp(e) {
this.inSelectBox = false;
this.isDotDown = false;
},
// 选框拖动
onMouseCropBoxDown(e) {
const { offsetX, offsetY } = e;
this.startCropMouseX = offsetX;
this.stertCropMouseY = offsetY;
this.inSelectBox = true;
},
// 点位移动
onMouseDownDot(e, localtion) {
e.stopPropagation();
this.currentDot = localtion;
this.isDotDown = true;
},
// 设置选区可见位置
setHeightView() {
const { offsetLeft, offsetTop, offsetHeight, offsetWidth } = this.$refs.cropBox;
this.$refs.clipImg.style.clipPath = this.getLabel(offsetLeft, offsetTop, offsetHeight, offsetWidth);
},
// 设置预览图片
setPreviewImg() {
const { offsetLeft, offsetTop, offsetHeight, offsetWidth } = this.$refs.cropBox;
this.$refs.previewImg.style.left = `-${offsetLeft}px`;
this.$refs.previewImg.style.top = `-${offsetTop}px`;
this.$refs.previewImg.style.clipPath = this.getLabel(offsetLeft, offsetTop, offsetHeight, offsetWidth);
},
// 获取裁切path
getLabel(offsetLeft, offsetTop, offsetHeight, offsetWidth) {
return `polygon(${offsetLeft}px ${offsetTop}px,
${offsetLeft + offsetWidth}px ${offsetTop}px,
${offsetLeft + offsetWidth}px ${offsetTop + offsetHeight}px,
${offsetLeft}px ${offsetTop + offsetHeight}px)`;
},
// 右移 获取拉伸宽度+cropBox自身宽度
rightMove(e) {
let x = e.clientX; //鼠标X坐标(用offsetX有bug)
const { offsetLeft } = this.$refs.box1;
const maxX = offsetLeft + WIDTH - 2;
if (x > maxX) {
x = maxX;
}
const { offsetWidth, offsetLeft: left } = this.$refs.cropBox;
// 上一次点到浏览器距离
const preX = offsetLeft + left + offsetWidth;
const addWidth = x - preX;
this.$refs.cropBox.style.width = `${offsetWidth + addWidth}px`;
},
// 上移
upMove(e) {
let y = e.clientY; //鼠标Y坐标
const { offsetTop } = this.$refs.box1;
if (y < offsetTop) {
y = offsetTop;
}
const { offsetHeight, offsetTop: top } = this.$refs.cropBox;
// 右侧线距离浏览器的距离-y
this.$refs.cropBox.style.height = `${offsetHeight + offsetTop + top - y}px`;
// y-parent距离浏览器的距离
this.$refs.cropBox.style.top = `${y - offsetTop}px`;
},
// 下移
downMove(e) {
let y = e.clientY;
const { offsetTop } = this.$refs.box1;
const maxY = offsetTop + HEIGHT - 2;
if (y > maxY) {
y = maxY;
}
const { offsetHeight, offsetTop: top } = this.$refs.cropBox;
// 上一次点到浏览器距离
const preY = offsetHeight + top + offsetTop;
const addWidth = y - preY;
this.$refs.cropBox.style.height = `${offsetHeight + addWidth}px`;
},
// 左移
leftMove(e) {
let x = e.clientX; //鼠标X坐标
const { offsetLeft } = this.$refs.box1;
if (x < offsetLeft) {
x = offsetLeft;
}
const { offsetWidth, offsetLeft: left } = this.$refs.cropBox;
// 右侧线距离浏览器的距离-x
this.$refs.cropBox.style.width = `${offsetWidth + offsetLeft + left - x}px`;
// x-parent距离浏览器的距离
this.$refs.cropBox.style.left = `${x - offsetLeft}px`;
}
}
};
</script>
<style lang="scss">
.wrap {
display: flex;
padding: 30px;
$boxWidth: 400px; //盒子大小
$boxHeight: 400px;
$defaultCropBoxWidth: 150px; //裁切框初始值
$defaultCropBoxHeight: 150px;
$dotWidth: 6px; //点
.box1 {
width: $boxWidth;
height: $boxHeight;
border: 1px solid #000;
position: relative;
box-sizing: border-box;
@mixin img {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
-webkit-user-drag: none !important;
user-select: none;
}
.img1 {
@include img;
opacity: 0.5;
}
.img2 {
@include img;
clip-path: polygon(
0 0,
$defaultCropBoxWidth 0,
$defaultCropBoxWidth $defaultCropBoxHeight,
0 $defaultCropBoxHeight
);
}
.cropBox {
position: absolute;
left: 0;
top: 0;
width: $defaultCropBoxWidth;
height: $defaultCropBoxHeight;
border: 1px solid #fff;
cursor: all-scroll;
$halfDotWidth: calc(6px / -2);
@mixin dot {
position: absolute;
width: $dotWidth;
height: $dotWidth;
background-color: #fff;
}
.leftUp {
@include dot;
top: $halfDotWidth;
left: $halfDotWidth;
cursor: nw-resize;
}
.up {
@include dot;
top: $halfDotWidth;
left: 50%;
margin-left: $halfDotWidth;
cursor: n-resize;
}
.rightUp {
@include dot;
top: $halfDotWidth;
right: $halfDotWidth;
cursor: ne-resize;
}
.right {
@include dot;
top: 50%;
margin-top: $halfDotWidth;
right: $halfDotWidth;
cursor: e-resize;
}
.rightDown {
@include dot;
bottom: $halfDotWidth;
right: $halfDotWidth;
cursor: se-resize;
}
.down {
@include dot;
bottom: $halfDotWidth;
left: 50%;
margin-left: $halfDotWidth;
cursor: s-resize;
}
.leftDown {
@include dot;
bottom: $halfDotWidth;
left: $halfDotWidth;
cursor: sw-resize;
}
.left {
@include dot;
top: 50%;
margin-top: $halfDotWidth;
left: $halfDotWidth;
cursor: w-resize;
}
}
}
.box2 {
width: $boxWidth;
height: $boxHeight;
margin-left: 50px;
position: relative;
img {
position: absolute;
left: 0;
top: 0;
width: 400px;
height: 400px;
display: block;
clip-path: polygon(
0 0,
$defaultCropBoxWidth 0,
$defaultCropBoxWidth $defaultCropBoxHeight,
0 $defaultCropBoxHeight
);
-webkit-user-drag: none !important;
user-select: none;
}
}
}
</style>
未完待续。。。。后续会增加一个获取的裁剪图片