前段时间写了一个类似于百度ICOR,可拖拽缩放图片并在图片上框选文字的功能,这里的拖拽缩放功能就用到了vue-drag-zoom组件,组件是从npm下载的VUE2代码,放在VUE3+vite项目里面也是可以兼容的,但是组件仅提供了禁止缩放的API且存在一定问题
附npm地址:vue-drag-zoom - npm该组件适用于对一个元素在某个区域内进行拖动/缩放. Latest version: 1.0.9, last published: 4 months ago. Start using vue-drag-zoom in your project by running `npm i vue-drag-zoom`. There are no other projects in the npm registry using vue-drag-zoom.https://www.npmjs.com/package/vue-drag-zoom
以下为改造后源码:
1.新增禁止拖拽API,与禁止缩放API整合
2.解决了缩放后图片不固定在中心点问题
3.解决了频繁禁止与恢复拖拽缩放时,图片回到初始位置的问题
<template>
<div ref="xx-drag-zoom" class="xx-drag-zoom" :style="dragZoomNodeStyle">
<slot></slot>
</div>
</template>
<script>
export default {
name: "xx-drag-zoom",
components: {},
props: {
/* 被操作的元素 start */
// X 坐标
left: {
type: Number,
default: 0,
},
// Y 坐标
top: {
type: Number,
default: 0,
},
// 宽度
width: Number,
// 高度
height: Number,
// 允许缩放
allowZoom: Boolean,
// 缩放比例
zoom: {
type: Number,
default: 1,
},
// 最大缩放比例
maxZoom: {
type: Number,
default: 2,
},
// 最小缩放比例
minZoom: {
type: Number,
default: 0.5,
},
// 缩放幅度
range: {
type: Number,
default: 0.1,
},
/* 被操作的元素 end */
/* 活动区域 start */
// 节点 (注: 传入节点后可以自动获取数据, 不需要再传坐标与宽高了)
areaNode: HTMLDivElement,
// X 坐标 (未设置 areaNode 时生效)
areaLeft: {
type: Number,
default: 0,
},
// Y 坐标 (未设置 areaNode 时生效)
areaTop: {
type: Number,
default: 0,
},
// 宽度 (未设置 areaNode 时生效)
areaWidth: {
type: Number,
default: 200,
},
// 高度 (未设置 areaNode 时生效)
areaHeight: {
type: Number,
default: 100,
},
/* 活动区域 end */
},
data() {
return {
currentZoom: this.zoom,
initLeft: this.left,
initTop: this.top,
lastPosition: {},
};
},
computed: {
// 被操作的元素节点
dragZoomNode() {
return this.$refs["xx-drag-zoom"];
},
// 活动区域数据
areaNodeData() {
let obj = {};
// 计算属性无法监听活动区域的宽高变化
const node = this.areaNode;
if (node) {
obj = {
left: node.clientLeft, //活动区域定位 ? 左边框宽度
top: node.clientTop,
width: node.offsetWidth, // 活动区域宽高
height: node.offsetHeight,
};
} else {
obj = {
left: this.areaLeft,
top: this.areaTop,
width: this.areaWidth,
height: this.areaHeight,
};
}
return obj;
},
// 设置样式
dragZoomNodeStyle() {
return {
transform: `scale(${this.currentZoom})`,
left: `${this.initLeft}px`,
top: `${this.initTop}px`,
width: this.width + "px",
height: this.height + "px",
cursor: this.allowZoom ? "move" : "default",
};
},
},
watch: {
zoom(val) {
this.currentZoom = val;
this.initStyle(val);
},
},
created() {},
mounted() {
this.dragZoomNode.addEventListener("mousedown", this.mousedown);
this.dragZoomNode.addEventListener("wheel", this.mousescroll);
this.initStyle("mounted");
},
beforeDestroy() {
this.dragZoomNode.removeEventListener("mousedown", null);
this.dragZoomNode.removeEventListener("wheel", null);
},
methods: {
// 鼠标点击事件
mousedown(evt) {
const areaW = this.areaNode
? this.areaNode.offsetWidth
: this.areaNodeData.width;
const areaH = this.areaNode
? this.areaNode.offsetHeight
: this.areaNodeData.height;
const {
offsetLeft: dragL,
offsetTop: dragT,
offsetWidth: dragW,
offsetHeight: dragH,
} = this.dragZoomNode; //缩放内容的宽高与到活动区域的宽高距离
const x = evt.clientX - dragL; //鼠标相对于图片的位置
const y = evt.clientY - dragT;
// 鼠标拖动事件
document.onmousemove = (evt) => {
const zoom = this.currentZoom; //缩放比
// 不允许拖动
if (!this.allowZoom) {
return;
}
let styleL = evt.clientX - x;
let styleT = evt.clientY - y;
// 当拖动元素宽度小于父元素时
if (dragW * zoom < areaW) {
// 注: 使用 scale 缩放后, 元素实际尺寸不会改变
const boundaryL = (dragW * zoom - dragW) / 2;
const boundaryR = areaW - (dragW + boundaryL);
// 左边界
if (styleL < boundaryL) {
styleL = boundaryL;
}
// 右边界
if (styleL > boundaryR) {
styleL = boundaryR;
}
} else {
// 注: 使用 scale 缩放后, 元素实际尺寸不会改变
const boundaryL = (dragW * zoom - dragW) / 2;
const boundaryR = -(dragW * zoom - areaW - boundaryL);
// 左边界
if (styleL > boundaryL) {
styleL = boundaryL;
}
// 右边界
if (styleL < boundaryR) {
styleL = boundaryR;
}
}
// 当拖动元素高度小于父元素时
if (dragH * zoom < areaH) {
// 注: 使用 scale 缩放后, 元素实际尺寸不会改变
const boundaryT = (dragH * zoom - dragH) / 2;
const boundaryB = areaH - (dragH + boundaryT);
// 上边界
if (styleT < boundaryT) {
styleT = boundaryT;
}
// 下边界
if (styleT > boundaryB) {
styleT = boundaryB;
}
} else {
// 注: 使用 scale 缩放后, 元素实际尺寸不会改变
const boundaryT = (dragH * zoom - dragH) / 2;
const boundaryB = -(dragH * zoom - areaH - boundaryT);
// 上边界
if (styleT > boundaryT) {
styleT = boundaryT;
}
// 下边界
if (styleT < boundaryB) {
styleT = boundaryB;
}
}
this.dragZoomNode.style.left = styleL + "px";
this.dragZoomNode.style.top = styleT + "px";
this.lastPosition.left = styleL;
this.lastPosition.top = styleT;
this.$emit("mousemove", evt);
};
document.onmouseup = () => {
document.onmousemove = null;
};
},
// 鼠标滚轮事件
mousescroll(evt) {
// 阻止默认行为
if (evt.preventDefault) {
evt.preventDefault();
} else {
evt.returnValue = false;
}
const { deltaY } = evt;
const {
left: areaL,
top: areaT,
// width: areaW,
// height: areaH,
} = this.areaNodeData;
const areaW = this.areaNode
? this.areaNode.offsetWidth
: this.areaNodeData.width;
const areaH = this.areaNode
? this.areaNode.offsetHeight
: this.areaNodeData.height;
const {
offsetLeft: dragL,
offsetTop: dragT,
offsetWidth: dragW,
offsetHeight: dragH,
} = this.dragZoomNode;
let zoom = this.currentZoom;
// 不允许缩放
if (!this.allowZoom) {
return;
}
// 上滑
if (deltaY < 0) {
if (zoom >= this.maxZoom) {
return;
}
zoom += this.range;
} else {
if (zoom <= this.minZoom) {
return;
}
zoom -= this.range;
}
this.currentZoom = Number(zoom.toFixed(1));
/* 边界判定 */
const subtractW = (dragW * this.currentZoom - dragW) / 2;
const subtractH = (dragH * this.currentZoom - dragH) / 2;
const currentL = dragL - subtractW;
const currentT = dragT - subtractW;
const currentR = dragL + dragW + subtractW;
const currentB = dragT + dragH + subtractH;
// 当拖动元素宽度小于父元素时
if (dragW * zoom < areaW) {
// 左边界判定
if (currentL < areaL) {
this.dragZoomNode.style.left = areaL + subtractW + "px";
}
// 右边界判定
if (currentR > areaW) {
this.dragZoomNode.style.left = areaW - dragW - subtractW + "px";
}
} else {
// 左边界判定
if (currentL > areaL) {
this.dragZoomNode.style.left = areaL + subtractW + "px";
}
// 右边界判定
if (currentR < areaW) {
this.dragZoomNode.style.left = areaW - dragW - subtractW + "px";
}
}
// 当拖动元素高度小于父元素时
if (dragH * zoom < areaH) {
// 上边界判定
if (currentT < areaT) {
this.dragZoomNode.style.top = areaT + subtractH + "px";
}
// 下边界判定
if (currentB > areaH) {
this.dragZoomNode.style.top = areaH - dragH - subtractH + "px";
}
} else {
// 上边界判定
if (currentT > areaT) {
this.dragZoomNode.style.top = areaT + subtractH + "px";
}
// 下边界判定
if (currentB < areaH) {
this.dragZoomNode.style.top = areaH - dragH - subtractH + "px";
}
}
this.$emit("mousescroll", evt);
},
// 样式初始化
initStyle(title) {
let tmpLeft = this.left;
let tmpTop = this.top;
const { offsetWidth: dragW, offsetHeight: dragH } = this.dragZoomNode;
tmpLeft = this.left - (dragW * (1 - this.zoom)) / 2;
tmpTop = this.left - (dragH * (1 - this.zoom)) / 2;
this.initLeft = tmpLeft || this.lastPosition.left;
this.initTop = tmpTop || this.lastPosition.top;
},
},
};
</script>
<style scoped>
.xx-drag-zoom {
position: absolute;
user-select: none;
}
</style>