区域标注分享

lxf2023-05-21 01:10:09

我正在参加「编程·启航计划」

背景

针对图片某区域内容进行矩形/多边形框选及标注,单张图片支持多区域标注。同时进行数据记录包含位置信息标签信息、原始图片信息。

主要功能交互

  1. 框选: 鼠标在图片区域时,按住左键进行拖动时代表正在框选位置。
  2. 预置标签: 框选完毕后弹出预置标签的标签框,选择完毕后隐藏标签框,同时在标签区域生成标签框。
  3. 可删除: 框选完毕后位置左上角出现“X”按钮,此按钮代表可进行删除此标签框。
  4. 拖动更改: 区域标注框可进行拖动及更改大小,且位置信息同步
  5. 区域标识:区域右上角需要有序号标识,点击同步高亮
  6. 区域标签联动: 图片标签更改或者删除与框选区域联动。
  7. 图片放大:需要支持图片放大进行框选,区域回显

区域标注分享

实现过程

1.分析

事件:鼠标按下@mousedown、鼠标拖动@mousemove、鼠标弹起@mouseup

原理:在图片上方增加一个div作为画布。区域框选,就是在画布上创建div,通过position: absolute;确定元素的位置left、top、width、height。拖拽、放大缩小、删除都是对div操作

实现

  • 定义 数组detailImgOption,
  • 创建 鼠标按下到鼠标弹起表示创建完成,就往数组中添加一条数据
  • 修改 计算鼠标的移动位置,然后赋值给
  • 删除 删除就detailImgOption中splice删除一条数据。

关键:只需要记录div的(x,y),(ex,ey)坐标,就可以计算出div的left、top、width、height

区域标注分享

2.具体代码

分为两个部分,html部分和事件处理部分

html部分
<div class="imageLabel-img-boxs">
  <!-- 图片 -->
  <img ref="Img" :src="picUrl" alt="">

  <!-- 区域 -->
  <div  id="regionImg"
        ref="regionImg"
        class="imageLabel-content"
        @mousedown="IMGmousedown"
        @mousemove="IMGmousemove"
        @mouseup="IMGmouseup">
          
    <!-- 框选区域 -->     
    <div  v-for="(n,i) in detailImgOption" :key="i"
          :id="n.id"
          class="imageLabel-imgdrop"
          :class="{'imageLabel-imgdrop-active':moveDropId===n.id}"
          :style="{'z-index':n.zindex,left:n.x+'px',top:n.y+'px',width: Math.abs(n.ex - n.x)+'px',height:Math.abs(n.ey - n.y)+'px',}"
          @mousedown.self="dropMousedown(n.id,$event)"
          @mousemove.self="dropMousemove"
          @mouseup="dropMouseup">
            
      <!-- 拖拽的点 -->
      <i v-for="(label,k) in 8" :key="k"
        class="imageLable-i"
        @mousedown="labelMousedown(n.id,$event)"
        @mousemove="labelMousemove(k,$event)"
        @mouseup="labelMouseup">
      </i>

      <!-- 标签order标识 -->
      <span class="region-img-label-order region-order">{{i+1}}</span>
    </div>
  </div>
</div>
事件处理部分

区域标注分享

    IMGmousedown (e) {
      if (this.flag_target || this.flag_label || this.flag_feature) {
        return
      }
      if (e.buttons === 1) {
        // offsetX/Y 只读属性提供该事件与目标节点的填充边缘之间鼠标指针的X/Y坐标中的偏移量
        // pageX/Y 只读属性返回事件相对于整个文档的水平/垂直坐标
        this.obj = {
          x: (e.pageX - this.imgLeft),
          y: (e.pageY - this.imgTop)
        }

        this.flag = true
        if (this.detailImgOption.length) {
          this.detailImgOption.forEach(n => {
            n.zindex = 0
          })
        }

        this.detailImgOption.push({
          id: `regin-div${this.regionCount}`,
          gradeValue: [],
          x: this.obj.x,
          y: this.obj.y,
          ex: 0,
          ey: 0,
          zindex: 100
        })
      }
    },
    IMGmousemove (e) {
      let offsetX = (e.pageX - this.imgLeft)
      let offsetY = (e.pageY - this.imgTop)
      if (this.flag) {
        let item = this.detailImgOption.find(n => n.id === `regin-div${this.regionCount}`)
        item.x = this.obj.x > offsetX ? offsetX : this.obj.x
        item.y = this.obj.y > offsetY ? offsetY : this.obj.y
        item.ex = this.obj.x > offsetX ? this.obj.x : offsetX
        item.ey = this.obj.y > offsetY ? this.obj.y : offsetY
      }
    },
    IMGmouseup (e) {
      this.flag = false
      this.flag_target = false
      this.flag_label = false
    },
    dropMousedown (id, e) {
      this.flag_target = true
      this.moveDropId = id
      let item = this.detailImgOption.find(n => n.id === this.moveDropId)
      this.obj = {
        x: e.offsetX,
        y: e.offsetY,
        pageX: e.pageX,
        pageY: e.pageY,
        itemx: item.x,
        itemy: item.y,
        itemex: item.ex,
        itemey: item.ey
      }
      if (this.detailImgOption.length) {
        this.detailImgOption.forEach(n => {
          n.zindex = n.id === id ? 100 : 0
        })
      }
    },
    dropMousemove (e) {
      if (this.flag_target && e.target.id === this.moveDropId) {
        let item = this.detailImgOption.find(n => n.id === this.moveDropId)

        // 边界优化2 开始
        // (e.pageX - this.obj.pageX) < 0 表示向左移动,可移动的距离范围:点击下去时候元素的x坐标
        if ((e.pageX - this.obj.pageX) < 0 && Math.abs(e.pageX - this.obj.pageX) > this.obj.itemx) {
          return
        }
        // (e.pageX - this.obj.pageX) > 0 表示向右移动,可移动的距离范围:图片长度-点击下去时候元素的ex坐标
        if ((e.pageX - this.obj.pageX) > 0 && Math.abs(e.pageX - this.obj.pageX) > (this.imgWidth - this.obj.itemex)) {
          return
        }
        // (e.pageY - this.obj.pageY) < 0 表示向上移动,可移动的距离范围:点击下去时候元素的y坐标
        if ((e.pageY - this.obj.pageY) < 0 && Math.abs(e.pageY - this.obj.pageY) > this.obj.itemy) {
          return
        }
        // (e.pageY - this.obj.pageY) > 0 表示向下移动,可移动的距离范围:图片高度-点击下去时候元素的ey坐标
        if ((e.pageY - this.obj.pageY) > 0 && Math.abs(e.pageY - this.obj.pageY) > (this.imgHeight - this.obj.itemey)) {
          // console.log('4', 4)
          return
        }
        // 边界优化2 结束
        item.x += (e.offsetX - this.obj.x)
        item.y += (e.offsetY - this.obj.y)
        item.ex += (e.offsetX - this.obj.x)
        item.ey += (e.offsetY - this.obj.y)
      }
    },
    dropMouseup (e) {
      this.flag_target = false
      this.flag_label = false
    },
    removeDrop (i) {
      this.detailImgOption.splice(i, 1)
    },
    labelMousedown (id, e) {
      e.preventDefault()
      this.flag_label = true
      this.obj = {
        pageX: (e.pageX - this.imgLeft),
        pageY: (e.pageY - this.imgTop),
        x: e.offsetX,
        y: e.offsetY
      }
      this.moveDropId = id
      if (this.detailImgOption.length) {
        this.detailImgOption.forEach(n => {
          n.zindex = n.id === id ? 100 : 0
        })
      }
    },
    labelMousemove (k, e) {
      if (this.flag_label) {
        let item = this.detailImgOption.find(n => n.id === this.moveDropId)
        this.k = k
        // 左上角
        if (k === 0) {
          item.x += (e.offsetX - this.obj.x)
          item.y += (e.offsetY - this.obj.y)
        }
        // 右上角
        if (k === 1) {
          item.y += (e.offsetY - this.obj.y)
          item.ex += (e.offsetX - this.obj.x)
        }
        // 右下角
        if (k === 2) {
          item.ex += (e.offsetX - this.obj.x)
          item.ey += (e.offsetY - this.obj.y)
        }
        // 左下角
        if (k === 3) {
          item.x += (e.offsetX - this.obj.x)
          item.ey += (e.offsetY - this.obj.y)
        }
        // 上中角
        if (k === 4) {
          item.y += (e.offsetY - this.obj.y)
        }
        // 右中角
        if (k === 5) {
          item.ex += (e.offsetX - this.obj.x)
        }
        // 下中角
        if (k === 6) {
          item.ey += (e.offsetY - this.obj.y)
        }
        // 左中角
        if (k === 7) {
          item.x += (e.offsetX - this.obj.x)
        }
      }
    },
    labelMouseup (e) {
      this.flag_label = false
      this.flag_target = false
    }

过程中遇到问题

  • 层级重叠覆盖问题:需要给元素设置层级z-index。
  • 移动的边界问题:需要对元素进行边界判断处理
  • 事件捕获、冒泡问题使用.self,如@mousedown.self="dropMousedown(n.id,$event)"
本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!