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 相关文章推荐
脚本收藏iframe
Jul 21 Javascript
[原创]保存的js无法执行的解决办法
Feb 25 Javascript
统计出现最多的字符次数的js代码
Dec 03 Javascript
Javascript事件实例详解
Nov 06 Javascript
JS调试必备的5个debug技巧
Mar 07 Javascript
jQuery简单图表peity.js使用示例
May 02 Javascript
Bootstrap基础学习
Jun 16 Javascript
JQuery 在文档中查找指定name的元素并移除的实现方法
May 19 Javascript
jQuery删除节点用法示例(remove方法)
Sep 08 Javascript
js中创建对象的几种方式
Feb 05 Javascript
angularjs中回车键触发某一事件的方法
Apr 24 Javascript
laydate只显示时分 不显示秒的功能实现方法
Sep 28 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套件 php版本升级方法(php5.5.24)
2015/05/10 PHP
php连接MSsql server的五种方法总结
2018/03/04 PHP
Django中通过定时任务触发页面静态化的处理方式
2018/08/29 PHP
兼容IE/Firefox/Opera/Safari的检测页面装载完毕的脚本Ext.onReady的实现
2009/07/14 Javascript
js同比例缩放图片的小例子
2013/10/30 Javascript
js判断undefined类型,undefined,null, 的区别详细解析
2013/12/16 Javascript
js对table的td进行相同内容合并示例详解
2013/12/27 Javascript
js实现选中复选框文字变色的方法
2015/08/14 Javascript
jquery mobile界面数据刷新的实现方法
2016/05/28 Javascript
给easyui datebox扩展一个清空的实例
2016/11/09 Javascript
基于javascript实现的快速排序
2016/12/02 Javascript
JavaScript字符集编码与解码详谈
2017/02/02 Javascript
JavaScript生成图形验证码
2020/08/24 Javascript
node中使用es5/6以及支持性与性能对比
2017/08/11 Javascript
解决option标签selected=&quot;selected&quot;属性失效的问题
2017/11/06 Javascript
angularJs提交文本框数据到后台的方法
2018/10/08 Javascript
vue.js自定义组件实现v-model双向数据绑定的示例代码
2020/01/08 Javascript
解决 window.onload 被覆盖的问题方法
2020/01/14 Javascript
[00:43]DOTA2小紫本全民票选福利PA至宝全方位展示
2014/11/25 DOTA
python中mechanize库的简单使用示例
2014/01/10 Python
浅谈python多线程和队列管理shell程序
2015/08/04 Python
Python实现各种排序算法的代码示例总结
2015/12/11 Python
Python数据结构之翻转链表
2017/02/25 Python
对python捕获ctrl+c手工中断程序的两种方法详解
2018/12/26 Python
python使用opencv在Windows下调用摄像头实现解析
2019/11/26 Python
Python 调用有道翻译接口实现翻译
2020/03/02 Python
Melijoe英国官网:法国儿童时尚网站
2016/11/18 全球购物
如何防止同一个帐户被多人同时登录
2013/08/01 面试题
民间借贷协议书范本
2014/10/01 职场文书
2015年推普周活动方案
2015/05/06 职场文书
结婚当天新郎保证书
2015/05/08 职场文书
鲁冰花观后感
2015/06/10 职场文书
优秀团员主要事迹范文
2015/11/05 职场文书
如何起草一份正确的合伙创业协议书?
2019/07/04 职场文书
python中使用 unittest.TestCase单元测试的用例详解
2021/08/30 Python
Vue如何清空对象
2022/03/03 Vue.js