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中的数学函数集合
May 08 Javascript
Prototype Object对象 学习
Jul 12 Javascript
使用JQuery库提供的扩展功能实现自定义方法
Sep 09 Javascript
js根据手机客户端浏览器类型,判断跳转官网/手机网站多个实例代码
Apr 30 Javascript
jQuery 获取屏幕高度、宽度的简单实现案例
May 17 Javascript
js 实现数值的千分位及保存小数方法(推荐)
Aug 01 Javascript
AngularJS ng-template寄宿方式用法分析
Nov 07 Javascript
微信小程序 页面跳转及数据传递详解
Mar 14 Javascript
基于vue,vue-router, vuex及addRoutes进行权限控制问题
May 02 Javascript
Vue 请求传公共参数的操作
Jul 31 Javascript
详解JavaScript 高阶函数
Sep 14 Javascript
小程序组件传值和引入sass的方法(使用vant Weapp组件库)
Nov 24 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
调整优化您的LAMP应用程序的5种简单方法
2011/06/26 PHP
PHP的fsockopen、pfsockopen函数被主机商禁用的解决办法
2014/07/08 PHP
php base64 编码与解码实例代码
2017/03/21 PHP
Javascript操纵Cookie实现购物车程序
2007/02/15 Javascript
Javascript this 的一些学习总结
2012/08/31 Javascript
jquery实现marquee效果(文字或者图片的水平垂直滚动)
2013/01/07 Javascript
javascript等号运算符使用详解
2015/04/16 Javascript
jquery实现滑动特效代码
2015/08/10 Javascript
JS创建事件的三种方法(实例代码)
2016/05/12 Javascript
老生常谈js动态添加事件--- 事件委托
2016/07/19 Javascript
js完整倒计时代码分享
2016/09/18 Javascript
详解vue slot插槽的使用方法
2017/06/13 Javascript
Vue实现内部组件轮播切换效果的示例代码
2018/04/07 Javascript
jQuery实现使用sort方法对json数据排序的方法
2018/04/17 jQuery
在vue2.0中引用element-ui组件库的方法
2018/06/21 Javascript
对Vue beforeRouteEnter 的next执行时机详解
2018/08/25 Javascript
react 中父组件与子组件双向绑定问题
2019/05/20 Javascript
浅入深出Vue之组件使用
2019/07/11 Javascript
js实现多图和单图上传显示
2019/12/18 Javascript
原生JavaScript实现弹幕组件的示例代码
2020/10/12 Javascript
python之PyMongo使用总结
2017/05/26 Python
python学习入门细节知识点
2018/03/29 Python
Python 实现两个服务器之间文件的上传方法
2019/02/13 Python
分享8点超级有用的Python编程建议(推荐)
2019/10/13 Python
Python装饰器实现方法及应用场景详解
2020/03/26 Python
Python第三方包PrettyTable安装及用法解析
2020/07/08 Python
5款实用的python 工具推荐
2020/10/13 Python
flask项目集成swagger的方法
2020/12/09 Python
Roxy荷兰官方网站:冲浪、滑雪板、服装和配件
2019/10/22 全球购物
中级会计职业生涯规划范文
2014/01/16 职场文书
工作散漫检讨书
2014/09/16 职场文书
疾病证明书
2015/06/19 职场文书
Nginx+Windows搭建域名访问环境的操作方法
2022/03/17 Servers
一文搞懂Redis中String数据类型
2022/04/03 Redis
进行数据处理的6个 Python 代码块分享
2022/04/06 Python
vue实现省市区联动 element-china-area-data插件
2022/04/22 Vue.js