vue+svg.js 实现图像标记

本文讲的是如何使用svg.js实现图像的标记。在本文中,将使用前端框架vueelement UI搭配实现功能。本文不再赘述如何搭建vue+element ui框架,框架在本文只是辅助作用,用户可以在其他框架中实现等同功能。本文中标记的实现需要依赖多个svg的插件,请至svg.js官网自行了解。
功能需求:

  1. svg画图,实现矩形、多边形画图;
  2. 图形可以resizedraggable
  3. 画布可以zoom in & zoom out
  4. 画图结束时弹出输入框,用户输入数据;
  5. 删除图形标记,删除画布;
  6. 监听键盘事件,实现快捷操作。

页面预览:
在这里插入图片描述
在这里插入图片描述

安装svg.js及相关插件

在项目中安装本项目中需要用的依赖,详细请参照官方文档。
以node为例:

svg.js npm install svg.js

svg.draw.js npm install svg.draw.js

svg.select.js npm install svg.select.js

svg.resize.js npm install svg.resize.js

svg.panzoom.js

svg.draggy.js

在vue项目中引入

完成以下两步就可以在项目中以this.$svg使用SVG啦

  1. 在项目中新建文件svg.js,输入以下代码;
import svgJS from 'svg.js'
import 'svg.draw.js'
import 'svg.panzoom.js'
import 'svg.draggable.js'
import 'svg.draggy.js'
import 'svg.select.js'
import 'svg.resize.js'

export default {
 install: Vue => {
   Vue.prototype.$svg = svgJS
 }
}
  1. 在main.js中引入;
 import svgJs from './utils/svg'
 Vue.use(svgJs)

创建画布

在需要标记的画面中创建画布。

<div id="myDrawing" class="draw" />
    // create canvas
    createCanvas(currentImg) {
      const _this = this
      const width = document.getElementById('myDrawing').clientWidth
      const persent = width / currentImg.width
      document.getElementsByClassName('label-area')[0].style.height = currentImg.height * persent + 'px'
      document.getElementsByClassName('label-area')[0].style.maxHeight = '85%'
      this.oDom = this.$svg('myDrawing').size('100%', '100%')
      this.mainImage = this.oDom.image(currentImg.url)
      this.mainImage.loaded(function(loader) {
        _this.oDom.size('100%', '100%')
        _this.opt.constraint.minX = 0
        _this.opt.constraint.minY = 0
        _this.opt.constraint.maxX = loader.width
        _this.opt.constraint.maxY = loader.height
        _this.oG = _this.oDom.group().add(this)
        _this.oDom.zoom(persent, { x: 0, y: 0 })
        const pzoom = _this.oDom.panZoom({ zoomMin: 0.2, zoomMax: 5 })
        _this.oDom.on('panStart', function(e) {
          console.log(e)
          if (_this.zoomIn) {
            console.log(pzoom.zoom())
            pzoom.zoom() + 0.5 < 5 ? pzoom.zoom(pzoom.zoom() + 0.5, new SVG.Point(e.detail.event.layerX/2, e.detail.event.layerY/2)) : ''
          } else if (_this.zoomOut) {
            pzoom.zoom() - 0.5 > 0.2 ? pzoom.zoom(pzoom.zoom() - 0.5, new SVG.Point(e.detail.event.layerX/2, e.detail.event.layerY/2)) : ''
          }
        })
        _this.oDom.on('panEnd', function(e) {
        })
        _this.oDom.on('zoom', function(lvl, focus) {
          console.log(pzoom.zoom())
        })
        if (_this.alldata.length !== 0 && _this.alldata !== null) {
          _this.showGraphic(_this.alldata)
        }
        _this.loading = false
      })
      this.initTagNameList()
      this.keyDownListener()
    },

画图/做标记

html

<div id="shape" class="label-shape">
              <el-tooltip v-if="hasSVG" class="item" effect="dark" content="矩形(R)" placement="top">
                <svg width="45" height="25" style="margin:10px 0;float:left;" version="1.1" xmlns="http://www.w3.org/2000/svg">
                  <rect x="0" y="0" width="45" height="25" style="fill:#e7f5ff;stroke:#96cffb;stroke-width:2px;cursor:pointer;" @click="changeType(1)" />
                </svg>
              </el-tooltip>
              <el-tooltip v-if="hasSVG && currentNode.data.labelType === '1'" class="item" effect="dark" content="多边形" placement="top">
                <svg width="50" height="25" style="margin:10px 15px;float:left;" version="1.1" xmlns="http://www.w3.org/2000/svg">
                  <polygon points="0,10,25,0,50,10,40,25,10,25" style="fill:#e7f5ff;stroke:#96cffb;stroke-width:1px;cursor:pointer;" @click="changeType(3)" />
                </svg>
              </el-tooltip>
            </div>

画图

// 更改图形
    changeType(val) {
      this.type = val
      // this.hasNode = true
      if (!this.hasNode) {
        this.hasNode = true
        switch (val) {
          case 1:
            this.drawRect()
            break
          case 2:
            this.drawParallelogram()
            break
          case 3:
            this.drawPoligon()
            break
          default:
            break
        }
      } else {
        this.$message({
          showClose: true,
          message: '当前节点已存在,请在标注区域进行标注。',
          type: 'warning'
        })
      }
    },
        // 画矩形
    drawRect() {
      this.drawing = true
      const _this = this
      this.disableAllDraggy()
      // const rect = this.oG.rect().draw().attr({
      //   fill: '#9ccbff30',
      //   stroke: '#409EFF',
      //   'stroke-width': '1'
      // })
      _this.oDom.panZoom(false)
      _this.clearResize()
      if (this.$store.state.settings.clickLabel) {
        const rect = this.oG.rect().draw().attr({
          fill: '#9ccbff30',
          stroke: '#409EFF',
          'stroke-width': '1'
        })
        rect.on('drawstop', function() {
        // remove listener
          _this.drawing = false
          _this.oDom.panZoom({ zoomMin: 0.2, zoomMax: 5 })
        })
        _this.commonEvent(1, rect)
      } else {
        const drawing = this.oG
        const rect = drawing.rect().attr({
          fill: '#9ccbff30',
          stroke: '#409EFF',
          'stroke-width': '1'
        })
        drawing.on('mousedown', function(e) {
        // e.stopPropagation()
          console.log('监听到鼠标点下事件')
          console.log(_this.drawing)
          if (_this.drawing) {
            rect.draw(e)
          }
        }, false)

        drawing.on('mouseup', function(e) {
          rect.draw('stop', e)
          console.log('监听到鼠标抬起事件')
        // _this.drawing = false
        // _this.oDom.panZoom({ zoomMin: 0.2, zoomMax: 5 })
        }, false)

        rect.on('drawstop', function() {
        // remove listener
          _this.drawing = false
          _this.oDom.panZoom({ zoomMin: 0.2, zoomMax: 5 })
        })
        _this.commonEvent(1, rect)
      }
    },
    // 画平行四边形
    drawParallelogram() {
      const _this = this
      const rect = this.oG.rect().draw().attr({
        fill: '#9ccbff30',
        stroke: '#409EFF',
        'stroke-width': '1'
      })
      // rect.transform({skewX: -45})
      // rect.rotate(45);
      // rect.rect().draw().attr({fill: 'red'}).transform({skewX: -45})
      // .scale(4)
      // .rotation(45);
      _this.commonEvent(1, rect)
    },
    // 画多边形
    drawPoligon() {
      const polygon = this.oG.polygon().draw().attr({
        fill: '#9ccbff30',
        stroke: '#409EFF',
        'stroke-width': '1'
      })
      this.commonEvent(3, polygon)
    },
    // 画已存在数据
    showGraphic(data) {
      const _this = this
      for (const i in data) {
        const polygon = this.oG.polygon(data[i].points).fill('#9ccbff30').attr({
          stroke: '#409EFF',
          'stroke-width': '1',
          id: data[i].id
        })
        _this.$svg.get(data[i].id).draggy(_this.opt.constraint)
        _this.commonEvent(2, polygon)
        _this.addResize(polygon)
      }
      _this.clearResize()
    },

公共事件

    // common  type = 1 :rect    2 : origin poligon  3: poligon
    commonEvent(type, polygon) {
      const _this = this
      // polygon.draggy(_this.opt.constraint)
      polygon.click(function(e) {
        // e.stopPropagation()
        _this.currentChild = polygon
        _this.clearResize()
        if (!this.drawing) {
          if (document.getElementById(polygon.node.id + 'g')) {
            document.getElementById(polygon.node.id + 'g').style.display = 'block'
          }
        }
        // if (document.getElementById(polygon.node.id + 'g')) {
        //   document.getElementById(polygon.node.id + 'g').style.display = 'block'
        // }
        // polygon.selectize().resize(_this.opt)
        // document.getElementById(_this.oG.node.id).lastChild.id = polygon.node.id + 'g'
        // document.getElementById(polygon.node.id + 'g').style.display = 'block'
        _this.selectAreaAndTag(polygon.node.id, _this.alldata, _this.tagNameList)
        // 去除hightLight
        // for (let i in this.alldata) {
        //     if (this.alldata[i].id === item.id) {
        //       this.$svg.get(this.alldata[i].id).fill('#9ccbff30')
        //     } else {
        //       this.$svg.get(this.alldata[i].id).fill('#9ccbff30')
        //     }
        // }
      })
      polygon.on('dblclick', function(e) {
        _this.currentChild = polygon
        _this.getNameInfo(_this.currentChild.node.id)
        _this.dialogVisble = true
      })
      polygon.on('mouseover', function(e) {
        // 去除其他节点hightLight
        for (const i in _this.alldata) {
          _this.$svg.get(_this.alldata[i].id).fill('#9ccbff30')
        }
        polygon.attr({
          fill: '#2d94ff7d',
          cursor: 'pointer'
        })
      })
      polygon.on('mouseout', function(e) {
        polygon.attr({
          fill: '#9ccbff30'
        })
      })
      polygon.on('drawstart', function(event) {
        _this.currentChild = polygon
        if (type === 3) {
          document.addEventListener('contextmenu', function(e) {
            e.preventDefault()
            polygon.draw('done')
            polygon.off('drawstart')
            return false
          })
        }
      })
      polygon.on('drawstop', function(event) {
        _this.changeData(type, polygon)
        _this.dialogVisble = true
        _this.hasNode = false
        _this.allDragable()
      })
      polygon.on('resizedone', function(e) {
        _this.changeData(type, polygon)
        _this.$forceUpdate()
      })
      polygon.on('dragstart', function(e) {
        _this.oDom.panZoom(false)
        e.stopPropagation()
      })
      polygon.on('dragend', function(e) {
        _this.oDom.panZoom({ zoomMin: 0.2, zoomMax: 5 })
        e.stopPropagation()
        _this.changeData(type, polygon)
        _this.$forceUpdate()
      })
    },

清空画布

// 清空画布
    clearSVG() {
      if (this.hasSVG) {
        // document.querySelector('svg').innerHTML = ''
        var self = document.getElementById('myDrawing')
        var parent = self.parentElement
        parent.removeChild(self)
        var para = document.createElement('div')
        para.id = 'myDrawing'
        para.setAttribute('class', 'draw')
        para.setAttribute('style', 'width:100%;height:100%')
        var labelArea = document.getElementById('labelArea')
        labelArea.appendChild(para)
        this.oG = this.oDom.group()
        this.alldata = []
        this.tagNameList = []
        this.hasSVG = false
      }
    },

清空resize

// 清空画布
  clearResize() {
      var id2 = document.getElementById(this.oG.node.id)
      var id2Div = id2.getElementsByTagName('g')
      var length = id2Div.length
      for (var i = 0; i < length; i++) {
        id2Div[i].style.display = 'none'
      }
    },

删除某一子节点

// 清空画布
  deleteChild() {
      const _this = this
      const child = document.getElementById(this.currentChild.node.id)
      document.getElementById(_this.oG.node.id).removeChild(child)
      _this.clearResize()
      for (const i in _this.alldata) {
        if (_this.alldata[i].id === this.currentChild.node.id) {
          _this.alldata.splice(i, 1)
        }
      }
      _this.dialogVisble = false
    },

全局快捷键的监听

// 全局快捷键的监听
    keyDownListener(str, rect, poligon) {
      var _this = this
      // 0:down   1:up
      document.onkeydown = function(e) {
        const key = window.event.keyCode
        console.log(key)
        _this.keyDownEvent(key, e, 0)
      }
      document.onkeyup = function(e) {
        const key = window.event.keyCode
        _this.keyDownEvent(key, e, 1)
      }
    },
     //  键盘快捷键事件  82:R  80:P  90:Z  88:X
    keyDownEvent(key, e, val) {
      if (!this.dialogVisble && val === 0 && this.hasSVG) {
        switch (key) {
          case 82:
            this.changeType(1)
            break
          case 80:
            if (this.currentNode.data.labelType === 1) this.changeType(3)
            break
          case 90:
            this.zoomIn = true
            break
          case 88:
            this.zoomOut = true
            break
          default:
            break
        }
      } else if (!this.dialogVisble && val === 1 && this.hasSVG) {
        switch (key) {
          case 90:
            this.zoomIn = false
            break
          case 88:
            this.zoomOut = false
            break
          default:
            break
        }
      }
    }
Logo

前往低代码交流专区

更多推荐