如何用vue实现一个矩形标记区域 rectangle marker
代码地址:vue-rectangle-marker
一、前言
一些cms系统经常会用到区域标记功能,所以写了个用vue实现的矩形标记区域,包含拖拽、放大缩小、重置功能。
二、实现结果
初始
标记
三、代码实现
<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打造一个移动端音乐播放器
写在前面 没错,这就是慕课网上的那个vue音乐播放器,后台是某音乐播放器的线上接口扒取,虽然这类项目写的人很多,但不得不说这还是个少有的适合vue提升的好项目,做这个项目除了想写一个比较大并且功能复杂 ...
- 实现CCLayer只显示一个矩形可见区域
转自:http://blog.csdn.net/while0/article/details/11004147 CCLayer的区域可能会比较大,怎样让它只显示其中一部分区域呢? 这个还是有很多场景 ...
- 如何用vue封装一个防用户删除的平铺页面的水印组件
需求 为了防止截图等安全问题,在web项目页面中生成一个平铺全屏的水印 要求水印内容为用户名,水印节点用户不能通过开发者工具等删除 效果 如上图 在body节点下插入水印DOM节点,水印节点覆盖在页面 ...
- 用Vue开发一个实时性时间转换功能,看这篇文章就够了
前言 最近有一个说法,如果你看见某个网站的某个功能,你就大概能猜出背后的业务逻辑是怎么样的,以及你能动手开发一个一毛一样的功能,那么你的前端技能算是进阶中高级水平了.比如咱们今天要聊的这个话题:如何用 ...
- text-overflow 与 word-wrap:设置使用一个省略标记...标示对象内文本的溢出。
text-overflow 与 word-wrap text-overflow用来设置是否使用一个省略标记(...)标示对象内文本的溢出. 语法: 但是text-overflow只是用来说明文字溢出时 ...
- 【CSS】如何用css做一个爱心
摘要:HTML的标签都比较简单,入门非常的迅速,但是CSS是一个需要我们深度挖掘的东西,里面的很多样式属性掌握几个常用的便可以实现很好看的效果,下面我便教大家如何用CSS做一个爱心. 前期预备知识: ...
- 用vue开发一个app(2,main.js)
昨天跟着vue的官网搭建了vue的一个脚手架,我也是第一次用VUE一切都在摸索阶段. 今天试着看下里面脚手架里面有点什么东西 先看看main.js 导入了3个模块 一个vue,一个app,还有rout ...
- 用vue开发一个app(4,一个久等了的文章)H5直播平台登录注册(1)
我上一篇关于vue的文章和这一篇时间隔了有点久了.最近终于写完了. 因为我一直想写个有点实绩的东西,而不是随便写一个教程一样东西.结合最近在项目中学到的经验和我的一点创意. 首先介绍下这是个什么! H ...
- php怎么做网站?如何用PHP开发一个完整的网站?
1.PHPer应具备的知识 (1)PHP知识: 熟练掌握基础函数,PHP语句(条件.循环),数组(排序.读取),函数(内部 构造),运算(数学 逻辑),面向对象(继承 接口 封装 多态静态属性)等. ...
随机推荐
- HotSpot VM 中的JIT分类
在HotSpot VM中内嵌有两个JIT编译器,分别为Client Compiler和Server Compiler,但大多数情况下我们简称为C1编译器和C2编译器.开发人员可以通过如下命令显式指定J ...
- 详细分析 Java 中实现多线程的方法有几种?(从本质上出发)
详细分析 Java 中实现多线程的方法有几种?(从本质上出发) 正确的说法(从本质上出发) 实现多线程的官方正确方法: 2 种. Oracle 官网的文档说明 方法小结 方法一: 实现 Runnabl ...
- 绝了!这款工具让SpringBoot不再需要Controller、Service、DAO、Mapper!
Dataway介绍 Dataway 是基于 DataQL 服务聚合能力,为应用提供的一个接口配置工具,使得使用者无需开发任何代码就配置一个满足需求的接口.整个接口配置.测试.冒烟.发布,一站式都通过 ...
- Burp Suite的安装
安装均在虚拟机环境下进行. 1.首先在浏览器找到java进行最新版本的安装. 2.然后找到burp suite 的安装包下载 不知道这一次 怎么直接跳过安装打开了.
- 国产化之路-安装WEB服务器
专题目录 国产化之路-统信UOS操作系统安装 国产化之路-国产操作系统安装.net core 3.1 sdk 国产化之路-安装WEB服务器 国产化之路-安装达梦DM8数据库 国产化之路-统信UOS + ...
- HTTP 的前世今生,那些不为人知的秘密
每个时代,都不会亏待会学习的人. 大家好,我是 yes. HTTP 协议在当今的互联网可谓是随处可见,一直默默的在背后支持着网络世界的运行,对于我们程序员来说 HTTP 更是熟悉不过. 平日里我们都说 ...
- 烦人的Null,你可以走开点了
1. Null 的问题 假设现在有一个需要三个参数的方法.其中第一个参数是必须的,后两个参数是可有可无的. 第一种情况,在我们调用这个方法的时候,我们只能传入两个参数,对第三个参数,我们在上下文里是没 ...
- python基础-面向对象opp
上述是实例化对象的一个过程. 类的定义和实例化: class Role(object): #定义一个类, class是定义类的语法,Role是类名,(object)是新式类的写法,必须这样写,以后再讲 ...
- 如何使用微软提供的TCHAR.H头文件?
转载:https://www.cnblogs.com/flyingspark/archive/2012/03/16/2399788.html 如何使用微软提供的TCHAR.H头文件? 如果你现在写的代 ...
- JVM 内存分配和占用
我们从一个简单示例来引出JVM的内存模型 简单示例 我从一个简单示例谈起这一块,我在看一篇文章的时候看到这么一个场景并且自己做了尝试,就是分配一个2M的数组,使用Xmx即最大内存为12M的话,会报错J ...