1. 程式人生 > 程式設計 >vue實現一個矩形標記區域(rectangle marker)的方法

vue實現一個矩形標記區域(rectangle marker)的方法

程式碼地址: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>&nbsp;&nbsp;
					<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',this.$refs.box);
							break;
						case "ne":
							this.move('n',this.$refs.box);
							this.move('e',this.$refs.box);
							break;
						case "sw":
							this.move('s',this.$refs.box);
							break;
						case "se":
							this.move('s',this.$refs.box);
							break;
						case "move":
							this.move('move',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,this.toFixed6(offsetTop + height,[this.toFixed6(offsetLeft,HEIGHT)]
					]
					// 矩形中點
					let centerPoint = [
						this.toFixed6(offsetLeft + 0.5 * 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,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實現矩形標記區域的資料請關注我們其它相關文章!