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 动态创建VML的方法
Oct 14 Javascript
js实时监听文本框状态的方法
Apr 26 Javascript
jquery阻止冒泡事件使用模拟事件
Sep 06 Javascript
jQuery获取对象简单实现方法小结
Oct 30 Javascript
jQuery选择器源码解读(三):tokenize方法
Mar 31 Javascript
js 实现一些跨浏览器的事件方法详解及实例
Oct 27 Javascript
JS绘制微信小程序画布时钟
Dec 24 Javascript
Web 开发中Ajax的Session 超时处理方法
Jan 19 Javascript
如何重置vue打印变量的显示方式
Dec 06 Javascript
Vue之Mixins(混入)的使用方法
Sep 24 Javascript
jquery添加div实现消息聊天框
Feb 08 jQuery
vue3.0封装轮播图组件的步骤
Mar 04 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获取文件内容最后一行示例
2014/01/09 PHP
thinkphp5框架前后端分离项目实现分页功能的方法分析
2019/10/08 PHP
php 使用ActiveMQ发送消息,与处理消息操作示例
2020/02/23 PHP
PHP使用Http Post请求发送Json对象数据代码解析
2020/07/16 PHP
php redis setnx分布式锁简单原理解析
2020/10/23 PHP
JQuery中对Select的option项的添加、删除、取值
2013/08/25 Javascript
让input框实现类似百度的搜索提示(基于jquery事件监听)
2014/01/31 Javascript
让alert不出现弹窗的两种方法
2014/05/18 Javascript
node.js实现逐行读取文件内容的代码
2014/06/27 Javascript
使用AmplifyJS组件配合JavaScript进行编程的指南
2015/07/28 Javascript
JavaScript构造函数详解
2015/12/27 Javascript
JavaScript开发者必备的10个Sublime Text插件
2016/02/27 Javascript
JavaScript模仿Pinterest实现图片预加载功能
2016/10/25 Javascript
js 去掉字符串前后空格实现代码集合
2017/03/25 Javascript
jQuery插件FusionCharts绘制的2D双柱状图效果示例【附demo源码】
2017/05/13 jQuery
详解Vue-cli中的静态资源管理(src/assets和static/的区别)
2018/06/19 Javascript
vue 输入电话号码自动按3-4-4分割功能的实现代码
2020/04/30 Javascript
[02:39]DOTA2英雄基础教程 极限穿梭编织者
2013/12/05 DOTA
[04:05]TI9战队采访 - Natus Vincere
2019/08/22 DOTA
python统计文本字符串里单词出现频率的方法
2015/05/26 Python
python制作最美应用的爬虫
2015/10/28 Python
python搭建虚拟环境的步骤详解
2016/09/27 Python
Python实现快速傅里叶变换的方法(FFT)
2018/07/21 Python
TensorFlow实现批量归一化操作的示例
2020/04/22 Python
html5中canvas学习笔记2-判断浏览器是否支持canvas
2013/01/06 HTML / CSS
HTML5+css3:3D旋转木马效果相册
2017/01/03 HTML / CSS
日本最大的旅游网站:Rakuten Travel(乐天旅游)
2018/08/02 全球购物
俄罗斯花园种植材料批发和零售网上商店:Беккер
2019/07/22 全球购物
美国折扣香水网站:The Perfume Spot
2020/12/12 全球购物
临床医学应届生求职信
2013/11/06 职场文书
不拖欠农民工工资承诺书
2014/03/31 职场文书
技术经济专业求职信
2014/09/03 职场文书
处级干部考察材料
2014/12/24 职场文书
学雷锋倡议书
2015/01/19 职场文书
青年文明号创建口号大全
2015/12/25 职场文书
Nginx利用Logrotate实现日志分割
2022/05/20 Servers