vue实现一个矩形标记区域(rectangle marker)的方法


Posted in Javascript onOctober 28, 2020

代码地址:vue-rectangle-marker

一、前言

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

二、实现结果

1.初始

vue实现一个矩形标记区域(rectangle marker)的方法

2.标记

vue实现一个矩形标记区域(rectangle marker)的方法

三、代码实现

<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实现一个矩形标记区域(rectangle marker)的方法的详细内容,更多关于vue实现矩形标记区域的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
JS 用6N±1法求素数 实例教程
Oct 20 Javascript
js实现杯子倒水问题自动求解程序
Mar 25 Javascript
js动态修改input输入框的type属性(实现方法解析)
Nov 13 Javascript
整理JavaScript对DOM中各种类型的元素的常用操作
May 05 Javascript
微信小程序开发之数据存储 参数传递 数据缓存
Apr 13 Javascript
angular2实现统一的http请求头方法
Aug 13 Javascript
elementUI Vue 单个按钮显示和隐藏的变换功能(两种方法)
Sep 04 Javascript
玩转vue的slot内容分发
Sep 22 Javascript
一步一步实现Vue的响应式(对象观测)
Sep 02 Javascript
Javascript基于OOP实实现探测器功能代码实例
Aug 26 Javascript
AJAX学习笔记
May 18 Javascript
vue数据字典取键值项目的字典问题
Apr 12 Vue.js
ant design vue datepicker日期选择器中文化操作
Oct 28 #Javascript
微信小程序picker组件两列关联使用方式
Oct 27 #Javascript
解决ant Design中this.props.form.validateFields未执行的问题
Oct 27 #Javascript
解决antd Form 表单校验方法无响应的问题
Oct 27 #Javascript
Antd表格滚动 宽度自适应 不换行的实例
Oct 27 #Javascript
解决Antd Table组件表头不对齐的问题
Oct 27 #Javascript
antd 表格列宽自适应方法以及错误处理操作
Oct 27 #Javascript
You might like
用PHP的ob_start();控制您的浏览器cache!
2007/02/14 PHP
基于asp+ajax和数据库驱动的二级联动菜单
2010/05/06 PHP
PHP开发中AJAX技术的简单应用
2015/12/11 PHP
浅析PHP中的i++与++i的区别及效率
2016/06/15 PHP
JavaScript网页制作特殊效果用随机数
2007/05/22 Javascript
JavaScript 设计模式之组合模式解析
2010/04/09 Javascript
让你的博文自动带上缩址的实现代码,方便发到微博客上
2010/12/28 Javascript
使用Javascript接收get传递的值的代码
2011/11/30 Javascript
Jquery实现列表(隔行换色,全选,鼠标滑过当前行)效果实例
2013/06/09 Javascript
基于OO的动画附加插件,可以实现弹跳、渐隐等动画效果 分享
2013/06/24 Javascript
jquery如何扑捉回车键触发的事件
2014/04/24 Javascript
js判断鼠标左、中、右键哪个被点击的方法
2015/01/27 Javascript
jQuery.prop() 使用详解
2015/07/19 Javascript
jQuery实现放大镜效果实例代码
2016/03/17 Javascript
vue2.0+webpack环境的构造过程
2016/11/08 Javascript
详解JS-- 浮点数运算处理
2016/11/28 Javascript
d3.js实现立体柱图的方法详解
2017/04/28 Javascript
详解vue-resource promise兼容性问题
2017/06/20 Javascript
JS库之Highlight.js的用法详解
2017/09/13 Javascript
详解vue 计算属性与方法跟侦听器区别(面试考点)
2018/04/23 Javascript
微信小程序第三方框架对比 之 wepy / mpvue / taro
2019/04/10 Javascript
javascript sort()对数组中的元素进行排序详解
2019/10/13 Javascript
[48:48]VGJ.T vs Liquid 2018国际邀请赛小组赛BO2 第二场 8.19
2018/08/21 DOTA
[43:03]完美世界DOTA2联赛PWL S2 PXG vs Magma 第二场 11.21
2020/11/24 DOTA
详解Python的Django框架中的templates设置
2015/05/11 Python
基于Python实现通过微信搜索功能查看谁把你删除了
2016/01/27 Python
Python递归函数实例讲解
2019/02/27 Python
移动端HTML5开发神器之vconsole详解
2020/12/15 HTML / CSS
台湾旅游网站:灿星旅游
2018/10/11 全球购物
党支部书记岗位责任制
2014/02/11 职场文书
大学军训感言800字
2014/02/27 职场文书
超市创业计划书
2014/04/24 职场文书
2014领导干部学习焦裕禄同志先进事迹思想汇报
2014/09/19 职场文书
三八妇女节寄语
2015/02/27 职场文书
兴趣班停课通知
2015/04/24 职场文书
nginx负载功能+nfs服务器功能解析
2022/02/28 Servers