前段时间写了一个类似于百度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.解决了频繁禁止与恢复拖拽缩放时,图片回到初始位置的问题
- <div ref="xx-drag-zoom" class="xx-drag-zoom" :style="dragZoomNodeStyle">
- <slot>slot>
- div>
-
- <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>