zoukankan      html  css  js  c++  java
  • 如何用vue实现一个矩形标记区域 rectangle marker

    代码地址:vue-rectangle-marker

    一、前言

    一些cms系统经常会用到区域标记功能,所以写了个用vue实现的矩形标记区域,包含拖拽、放大缩小、重置功能。

    二、实现结果

    1. 初始
      init.jpg

    2. 标记
      mark.jpg

    三、代码实现

    <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="{  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>&nbsp;&nbsp;
    					<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,
    					 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,
    						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 {
    		 100%;
    		height: 100%;
    		display: flex;
    		flex-direction: column;
    		align-items: center;
    		.mark-wrap {
    			position: relative;
    			.img-responsive {
    				display: inline-block;
    				max- 100%;
    				max-height: 100%;
    			}
    			.draw-rect {
    				position: absolute;
    				top: 0;
    				left: 0;
    				bottom: 0;
    				right: 0;
    				 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;
    			 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;
    			 0px;
    			height: 0px;
    			opacity: 0.5;
    			z-index: 149;
    			cursor: move;
    			border: 1px solid #f00;
    			.upleftbtn,
    			.uprightbtn,
    			.downleftbtn,
    			.downrightbtn {
    				 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>
    
    1. 背景图传入,图片自适应处理。
    2. 定义drag标记为,添加开始标记、重置按钮。
    3. 创建box区域,不同状态(change、moving、active),对应不同id。
    4. box可移动距离,计算边界。
    5. 四角放大缩小的功能。
    6. 生成结果,精确到6位小数,这样可以使得复原标记区域的时候误差最小。

    四、觉得有帮助的,麻烦给个赞哦,谢谢!

  • 相关阅读:
    SpringBoot系列之切换log4j日志框架
    SpringBoot系列之日志框架使用教程
    SpringBoot系列之集成logback实现日志打印(篇二)
    源码学习系列之SpringBoot自动配置(篇二)
    SpringBoot系列之@Conditional注解用法简介
    7.Maven命令
    6.Maven构建过程的各个环节
    5.Maven坐标
    4.用IntelliJ IDEA 创建Maven Web
    3.用IntelliJ IDEA 创建Maven
  • 原文地址:https://www.cnblogs.com/hackftz/p/13884004.html
Copyright © 2011-2022 走看看