涉及知识点: 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
clip-path - CSS:层叠样式表 | 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);},// 获取裁切pathgetLabel(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;// 右侧线距离浏览器的距离-ythis.$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;// 右侧线距离浏览器的距离-xthis.$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);},// 获取裁切pathgetLabel(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;// 右侧线距离浏览器的距离-ythis.$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;// 右侧线距离浏览器的距离-xthis.$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>
未完待续。。。。后续会增加一个获取的裁剪图片