代码地址: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>
  1. 背景图传入,图片自适应处理。
  2. 定义drag标记为,添加开始标记、重置按钮。
  3. 创建box区域,不同状态(change、moving、active),对应不同id。
  4. box可移动距离,计算边界。
  5. 四角放大缩小的功能。
  6. 生成结果,精确到6位小数,这样可以使得复原标记区域的时候误差最小。

四、觉得有帮助的,麻烦给个赞哦,谢谢!

如何用vue实现一个矩形标记区域 rectangle marker的更多相关文章

  1. 如何用vue打造一个移动端音乐播放器

    写在前面 没错,这就是慕课网上的那个vue音乐播放器,后台是某音乐播放器的线上接口扒取,虽然这类项目写的人很多,但不得不说这还是个少有的适合vue提升的好项目,做这个项目除了想写一个比较大并且功能复杂 ...

  2. 实现CCLayer只显示一个矩形可见区域

    转自:http://blog.csdn.net/while0/article/details/11004147 CCLayer的区域可能会比较大,怎样让它只显示其中一部分区域呢?  这个还是有很多场景 ...

  3. 如何用vue封装一个防用户删除的平铺页面的水印组件

    需求 为了防止截图等安全问题,在web项目页面中生成一个平铺全屏的水印 要求水印内容为用户名,水印节点用户不能通过开发者工具等删除 效果 如上图 在body节点下插入水印DOM节点,水印节点覆盖在页面 ...

  4. 用Vue开发一个实时性时间转换功能,看这篇文章就够了

    前言 最近有一个说法,如果你看见某个网站的某个功能,你就大概能猜出背后的业务逻辑是怎么样的,以及你能动手开发一个一毛一样的功能,那么你的前端技能算是进阶中高级水平了.比如咱们今天要聊的这个话题:如何用 ...

  5. text-overflow 与 word-wrap:设置使用一个省略标记...标示对象内文本的溢出。

    text-overflow 与 word-wrap text-overflow用来设置是否使用一个省略标记(...)标示对象内文本的溢出. 语法: 但是text-overflow只是用来说明文字溢出时 ...

  6. 【CSS】如何用css做一个爱心

    摘要:HTML的标签都比较简单,入门非常的迅速,但是CSS是一个需要我们深度挖掘的东西,里面的很多样式属性掌握几个常用的便可以实现很好看的效果,下面我便教大家如何用CSS做一个爱心. 前期预备知识: ...

  7. 用vue开发一个app(2,main.js)

    昨天跟着vue的官网搭建了vue的一个脚手架,我也是第一次用VUE一切都在摸索阶段. 今天试着看下里面脚手架里面有点什么东西 先看看main.js 导入了3个模块 一个vue,一个app,还有rout ...

  8. 用vue开发一个app(4,一个久等了的文章)H5直播平台登录注册(1)

    我上一篇关于vue的文章和这一篇时间隔了有点久了.最近终于写完了. 因为我一直想写个有点实绩的东西,而不是随便写一个教程一样东西.结合最近在项目中学到的经验和我的一点创意. 首先介绍下这是个什么! H ...

  9. php怎么做网站?如何用PHP开发一个完整的网站?

    1.PHPer应具备的知识 (1)PHP知识: 熟练掌握基础函数,PHP语句(条件.循环),数组(排序.读取),函数(内部 构造),运算(数学 逻辑),面向对象(继承 接口 封装 多态静态属性)等. ...

随机推荐

  1. 动态加载dll的实现+远线程注入

    1.在目标进程中申请内存 2.向目标进程内存中写入shellcode(没有特征,编码比较麻烦) 3.创建远线程执行shellcode 之前可以看到shellcode很难编写还要去依赖库,去字符串区等等 ...

  2. Docker数据卷和数据卷容器

    是什么 数据卷设计的目的,在于数据的永久化,他完全独立于容器的生存周期,因此,Docker不会在容器删除时删除其挂载的数据卷,也不会存在类似的垃圾收集机制对容器引用的数据卷进行处理.类似我们Redis ...

  3. list、set、map的区别和联系

    结构特点 List和Set是存储单列数据的集合,Map是存储键值对这样的双列数据的集合: List中存储的数据是有顺序的,并且值允许重复:Map中存储的数据是无序的,它的键是不允许重复的,但是值是允许 ...

  4. 极简 Node.js 入门 - 4.3 可读流

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  5. shiro安全框架和spring整合

    上干货......... 整合spring的配置文件 <?xml version="1.0" encoding="UTF-8"?><beans ...

  6. Python练习题 020:累积累加

    [Python练习题 020] 求1+2!+3!+...+20!的和 -------------------------------------------------- 据说这题是"累积累 ...

  7. JDBC Java 连接 MySQL 数据库

    MySQL 版本:Server version: 5.7.17-log MySQL Community Server (GPL) 用于测试的 MySQL 数据库:game 查看数据库中的表 mysql ...

  8. LCD1602 库函数

    LCD1602 库函数 This library allows an Arduino board to control LiquidCrystal displays (LCDs) based on t ...

  9. Jmeter之『JSR223脚本』

    Json处理(通过JS) 对于Json字符串,需要使用单引号『''』(因为Json中已存在双引号) // String转为Object var jsonObj = JSON.parse('${data ...

  10. 如何查找一个为NULL的MYSQL字段

    前言:在做这个题目 https://www.cnblogs.com/pipihao/p/13786304.html 因为之前 我好像没有接触过什么 为NULL字段的查询,细节不小 WHERE 字段 I ...