vue實現一個矩形標記區域(rectangle marker)的方法
阿新 • • 發佈:2020-10-28
程式碼地址: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',this.$refs.box); break; case "ne": this.move('n',this.$refs.box); this.move('e',this.$refs.box); break; case "sw": this.move('s',this.$refs.box); break; case "se": this.move('s',this.$refs.box); break; case "move": this.move('move',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,this.toFixed6(offsetTop + height,[this.toFixed6(offsetLeft,HEIGHT)] ] // 矩形中點 let centerPoint = [ this.toFixed6(offsetLeft + 0.5 * 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,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實現一個矩形標記區域(rectangle marker)的方法的詳細內容,更多關於vue實現矩形標記區域的資料請關注我們其它相關文章!