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 相关文章推荐
javascript 获取表单file全路径
Dec 31 Javascript
jquery实现图片水平滚动效果代码分享
Aug 26 Javascript
简单实现js无缝滚动效果
Feb 05 Javascript
jQuery绑定事件方法及区别(bind,click,on,live,one)
Aug 14 jQuery
基于ES6 Array.of的用法(实例讲解)
Sep 05 Javascript
jQuery实现的事件绑定功能基本示例
Oct 11 jQuery
Angular数据绑定机制原理
Apr 17 Javascript
通过函数作用域和块级作用域看javascript的作用域链
Aug 05 Javascript
解决vue-router在同一个路由下切换,取不到变化的路由参数问题
Sep 01 Javascript
基于Vue-cli快速搭建项目的完整步骤
Nov 03 Javascript
JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法详解【圆形情况】
Dec 13 Javascript
nginx配置域名后的二级目录访问不同项目的配置操作
Nov 06 Javascript
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实现信用卡校验位算法THE LUHN MOD-10示例
2014/05/07 PHP
CodeIgniter CLI模式简介
2014/06/17 PHP
基于jQuery的烟花效果(运动相关)点击屏幕出烟花
2012/06/14 Javascript
浅析Js(Jquery)中,字符串与JSON格式互相转换的示例(直接运行实例)
2013/07/09 Javascript
JavaScript调用ajax获取文本文件内容实现代码
2014/03/28 Javascript
jQuery动画效果animate和scrollTop结合使用实例
2014/04/02 Javascript
jQuery实现锚点scoll效果实例分析
2015/03/10 Javascript
JavaScript中解决多浏览器兼容性23个问题的快速解决方法
2016/05/19 Javascript
让编辑器支持word复制黏贴、截屏的js代码
2016/10/17 Javascript
微信小程序 MINA文件结构
2016/10/17 Javascript
前端开发之CSS原理详解
2017/03/11 Javascript
nodejs Assert中equal(),strictEqual(),deepEqual(),strictDeepEqual()比较
2017/09/18 NodeJs
bootstrap table实现双击可编辑、添加、删除行功能
2017/09/27 Javascript
解决vue组件中使用v-for出现告警问题及v for指令介绍
2017/11/11 Javascript
浅谈Koa2框架利用CORS完成跨域ajax请求
2018/03/06 Javascript
详解vantUI框架在vue项目中的应用踩坑
2018/12/06 Javascript
Vue响应式原理Observer、Dep、Watcher理解
2019/06/06 Javascript
利用python模拟sql语句对员工表格进行增删改查
2017/07/05 Python
使用Python爬了4400条淘宝商品数据,竟发现了这些“潜规则”
2018/03/23 Python
Tensorflow中使用tfrecord方式读取数据的方法
2018/06/19 Python
python调用webservice接口的实现
2019/07/12 Python
python中eval与int的区别浅析
2019/08/11 Python
python 检测nginx服务邮件报警的脚本
2020/12/31 Python
HTML 5 input placeholder 属性如何完美兼任ie
2014/05/12 HTML / CSS
美国女士泳装店:Swimsuits For All
2017/03/02 全球购物
澳大利亚票务和娱乐市场领导者:Ticketmaster
2017/03/03 全球购物
如何手工释放资源
2013/12/15 面试题
办公室年终个人自我评价
2013/10/28 职场文书
大专应届生个人的自我评价
2013/11/21 职场文书
工业学校毕业生自荐书
2014/01/03 职场文书
元旦晚会主持词
2014/03/24 职场文书
球队口号
2014/06/18 职场文书
2015出纳试用期工作总结
2014/12/12 职场文书
python 通过使用Yolact训练数据集
2021/04/06 Python
MySQL系列之三 基础篇
2021/07/02 MySQL
Nginx利用Logrotate实现日志分割
2022/05/20 Servers