vue实现一个矩形标记区域(rectangle marker)的方法
时间:2022-07-21 11:08:51|栏目:vue|点击: 次
代码地址:vue-rectangle-marker
一、前言
一些cms系统经常会用到区域标记功能,所以写了个用vue实现的矩形标记区域,包含拖拽、放大缩小、重置功能。
二、实现结果
1.初始
2.标记
三、代码实现
<template> <div class="rectangle-marker"> <div class="mark-wrap"> <img ref="backImg" :src="imgUrl" class="img-responsive" alt="响应式图像" @load="onload"> <div class="draw-rect" :class="{ 'no-event': disabled }" @mousemove="mouseMove" @mousedown="mouseDown" @mouseup="mouseUp"> <div ref="box" v-if="boxVisible" :id="boxId" class="box" :style="{ width: boxW + 'px', height: boxH + 'px', left: boxL + 'px', top: boxT + 'px' }"> <div id="upleftbtn" class="upleftbtn" @mousedown="onUpleftbtn"></div> <div id="uprightbtn" class="uprightbtn" @mousedown="onUpRightbtn"></div> <div id="downleftbtn" class="downleftbtn" @mousedown="onDownleftbtn"></div> <div id="downrightbtn" class="downrightbtn" @mousedown="onDownRightbtn"></div> </div> </div> <transition name="fade"> <div v-if="showBtns && !markFlag" class="act-btns" @mouseleave="mouseLeave"> <button @click="mark">mark</button> <button @click="reset">reset</button> </div> </transition> </div> </div> </template> <script> export default { name: 'rectangleMarker', data() { return { imgW: 0, imgH: 0, showBtns: true, markFlag: false, // 鼠标事件属性 dragging: false, startX: undefined, startY: undefined, diffX: undefined, diffY: undefined, obj: null, //当前操作对象 box: null, //要处理的对象 backImgRect: null, boxId: '', boxW: 0, boxH: 0, boxL: 0, boxT: 0, boxVisible: false } }, props: { imgUrl: { type: String, required: true, default: '' }, disabled: { type: Boolean, default: false }, value: { type: Array, default: function () { return [] } } }, methods: { onload() { let rect = this.$refs.backImg.getBoundingClientRect() this.backImgRect = { height: rect.height, width: rect.width } // console.log("initConfig -> this.backImgRect", this.backImgRect) if (this.value === '' || this.value === undefined || this.value === null || (Array.isArray(this.value) && this.value.length === 0)) { return } this.initData(this.value) }, mouseLeave() { this.showBtns = false }, mark() { this.markFlag = true }, reset() { this.boxVisible = false this.boxId = '' this.boxH = 0 this.boxW = 0 this.boxL = 0 this.boxT = 0 }, initData(data) { if (data === '' || data === undefined || data === null || (Array.isArray(data) && data.length === 0)) { return } this.boxId = 'changeBox' this.boxL = data[0][0] * this.backImgRect.width this.boxT = data[0][1] * this.backImgRect.height this.boxH = (data[3][1] - data[0][1]) * this.backImgRect.height this.boxW = (data[1][0] - data[0][0]) * this.backImgRect.width this.boxVisible = true }, mouseDown(e) { if (!this.markFlag && !this.boxVisible) { return } this.startX = e.offsetX; this.startY = e.offsetY; // 如果鼠标在 box 上被按下 if (e.target.className.match(/box/)) { // 允许拖动 this.dragging = true; // 设置当前 box 的 id 为 movingBox if (this.boxId !== 'movingBox') { this.boxId = 'movingBox' } // 计算坐标差值 this.diffX = this.startX this.diffY = this.startY } else { if (this.boxId === 'changeBox') { return } this.boxId = 'activeBox' this.boxT = this.startY this.boxL = this.startX this.boxVisible = true } }, mouseMove(e) { if (!this.markFlag && !this.boxVisible) { if (!this.backImgRect) { return } let toRight = this.backImgRect.width - e.offsetX let toTop = e.offsetY if (toRight <= 100 && toTop <= 40) { this.showBtns = true } return } let toRight = this.backImgRect.width - e.offsetX let toTop = e.offsetY if (toRight <= 100 && toTop <= 40) { this.showBtns = true return } // 更新 box 尺寸 if (this.boxId === 'activeBox') { this.boxW = e.offsetX - this.startX this.boxH = e.offsetY - this.startY } // 移动,更新 box 坐标 if (this.boxId === 'movingBox' && this.dragging) { let realTop = (e.offsetY + e.target.offsetTop - this.diffY) > 0 ? (e.offsetY + e.target.offsetTop - this.diffY) : 0 let realLeft = (e.offsetX + e.target.offsetLeft - this.diffX) > 0 ? (e.offsetX + e.target.offsetLeft - this.diffX) : 0 let maxTop = this.backImgRect.height - this.$refs.box.offsetHeight let maxLeft = this.backImgRect.width - this.$refs.box.offsetWidth realTop = realTop >= maxTop ? maxTop : realTop realLeft = realLeft >= maxLeft ? maxLeft : realLeft this.boxT = realTop; this.boxL = realLeft; } if (this.obj) { e = e || window.event; var location = { x: e.x || e.offsetX, y: e.y || e.offsetY } switch (this.obj.operateType) { case "nw": this.move('n', location, this.$refs.box); this.move('w', location, this.$refs.box); break; case "ne": this.move('n', location, this.$refs.box); this.move('e', location, this.$refs.box); break; case "sw": this.move('s', location, this.$refs.box); this.move('w', location, this.$refs.box); break; case "se": this.move('s', location, this.$refs.box); this.move('e', location, this.$refs.box); break; case "move": this.move('move', location, this.box); break; } } }, mouseUp() { if (!this.markFlag && !this.boxVisible) { return } // 禁止拖动 this.dragging = false; if (this.boxId === 'activeBox') { if (this.$refs.box) { this.boxId = 'changeBox' if (this.$refs.box.offsetWidth < 3 || this.$refs.box.offsetHeight < 3) { this.boxVisible = false this.boxId = '' } } } else { if (this.$refs.box && this.boxId === 'movingBox') { this.boxId = 'changeBox' if (this.$refs.box.offsetWidth < 3 || this.$refs.box.offsetHeight < 3) { this.boxVisible = false this.boxId = '' } } } if (this.boxVisible) { this.getHotData() document.body.style.cursor = "auto"; this.obj = null; this.markFlag = false } else { this.markFlag = true } }, getHotData() { let target = this.$refs.box if (target) { let { offsetTop, offsetLeft } = target let { width: WIDTH, height: HEIGHT } = this.backImgRect let { width, height } = target.getBoundingClientRect() // 矩形区域 角点位置(百分比) let data = [ [this.toFixed6(offsetLeft, WIDTH), this.toFixed6(offsetTop, HEIGHT)], [this.toFixed6(offsetLeft + width, WIDTH), this.toFixed6(offsetTop, HEIGHT)], [this.toFixed6(offsetLeft + width, WIDTH), this.toFixed6(offsetTop + height, HEIGHT)], [this.toFixed6(offsetLeft, WIDTH), this.toFixed6(offsetTop + height, HEIGHT)] ] // 矩形中点 let centerPoint = [ this.toFixed6(offsetLeft + 0.5 * width, WIDTH), this.toFixed6(offsetTop + 0.5 * height, HEIGHT) ] let hotData = { data, centerPoint } console.log("getHotData -> hotData", hotData) console.log(JSON.stringify(hotData)); } }, toFixed6(v1, v2) { return (v1 / v2).toFixed(6) }, move(type, location, tarobj) { switch (type) { case 'n': { let add_length = this.clickY - location.y; this.clickY = location.y; let length = parseInt(tarobj.style.height) + add_length; tarobj.style.height = length + "px"; let realTop = this.clickY > 0 ? this.clickY : 0 let maxTop = this.backImgRect.height - parseInt(tarobj.style.height) realTop = realTop >= maxTop ? maxTop : realTop tarobj.style.top = realTop + "px"; break; } case 's': { let add_length = this.clickY - location.y; this.clickY = location.y; let length = parseInt(tarobj.style.height) - add_length; let maxHeight = this.backImgRect.height - parseInt(tarobj.style.top) let realHeight = length > maxHeight ? maxHeight : length tarobj.style.height = realHeight + "px"; break; } case 'w': { var add_length = this.clickX - location.x; this.clickX = location.x; let length = parseInt(tarobj.style.width) + add_length; tarobj.style.width = length + "px"; let realLeft = this.clickX > 0 ? this.clickX : 0 let maxLeft = this.backImgRect.width - parseInt(tarobj.style.width) realLeft = realLeft >= maxLeft ? maxLeft : realLeft tarobj.style.left = realLeft + "px"; break; } case 'e': { let add_length = this.clickX - location.x; this.clickX = location.x; let length = parseInt(tarobj.style.width) - add_length; let maxWidth = this.backImgRect.width - parseInt(tarobj.style.left) let realWidth = length > maxWidth ? maxWidth : length tarobj.style.width = realWidth + "px"; break; } } }, onUpleftbtn(e) { e.stopPropagation(); this.onDragDown(e, "nw"); }, onUpRightbtn(e) { e.stopPropagation(); this.onDragDown(e, "ne"); }, onDownleftbtn(e) { e.stopPropagation(); this.onDragDown(e, "sw"); }, onDownRightbtn(e) { e.stopPropagation(); this.onDragDown(e, "se"); }, onDragDown(e, type) { e = e || window.event; this.clickX = e.x || e.offsetX; this.clickY = e.y || e.offsetY; this.obj = window; this.obj.operateType = type; this.box = this.$refs.box; return false; } }, } </script> <style lang="less" scoped> .rectangle-marker { width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; .mark-wrap { position: relative; .img-responsive { display: inline-block; max-width: 100%; max-height: 100%; } .draw-rect { position: absolute; top: 0; left: 0; bottom: 0; right: 0; width: 100%; height: 100%; z-index: 99; user-select: none; &.no-event { pointer-events: none; } } } .act-box { margin-top: 10px; display: flex; } .act-btns { position: absolute; right: 0; top: 0; z-index: 199; padding: 0 10px; height: 40px; width: 100px; display: flex; align-items: center; justify-content: center; } .fade-enter-active { animation: hide-and-show .5s; } .fade-leave-active { animation: hide-and-show .5s reverse; } @keyframes hide-and-show { 0% { opacity: 0; } 100% { opacity: 1; } } } </style> <style lang="less"> .rectangle-marker { .box { position: absolute; width: 0px; height: 0px; opacity: 0.5; z-index: 149; cursor: move; border: 1px solid #f00; .upleftbtn, .uprightbtn, .downleftbtn, .downrightbtn { width: 10px; height: 10px; border: 1px solid steelblue; position: absolute; z-index: 5; background: whitesmoke; border-radius: 10px; } .upleftbtn { top: -5px; left: -5px; cursor: nw-resize; } .uprightbtn { top: -5px; right: -5px; cursor: ne-resize; } .downleftbtn { left: -5px; bottom: -5px; cursor: sw-resize; } .downrightbtn { right: -5px; bottom: -5px; cursor: se-resize; } } } </style>
- 背景图传入,图片自适应处理。
- 定义drag标记为,添加开始标记、重置按钮。
- 创建box区域,不同状态(change、moving、active),对应不同id。
- box可移动距离,计算边界。
- 四角放大缩小的功能。
- 生成结果,精确到6位小数,这样可以使得复原标记区域的时候误差最小。
四、觉得有帮助的,麻烦给个赞哦,谢谢!
上一篇:在Vue中用canvas实现二维码和图片合成海报的方法
栏 目:vue
下一篇:在vue中v-for循环遍历图片不显示错误的解决方案
本文标题:vue实现一个矩形标记区域(rectangle marker)的方法
本文地址:http://www.codeinn.net/misctech/208404.html