vue实现一个矩形标记区域(rectangle marker)的方法

代码地址:vue-rectangle-marker

一、前言

一些cms系统经常会用到区域标记功能,所以写了个用vue实现的矩形标记区域,包含拖拽、放大缩小、重置功能。

二、实现结果

1.初始

2.标记

三、代码实现

<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', 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实现矩形标记区域的资料请关注其它相关文章!

以上是 vue实现一个矩形标记区域(rectangle marker)的方法 的全部内容, 来源链接: utcz.com/p/238306.html

回到顶部