fabric.js 组件 图片上传裁剪并进行自定义区域标记

目录

0. 前言

1. 安装fabric与引入

2. fabric组件的使用

3. 属性相关设置

4. 初始化加载

4. 方法

5. 全代码


0. 前言

利用fabric组件,实现图片上传、图片”裁剪“、自定义的区域标记一系列操作

先放一张效果图吧👇

1. 安装fabric与引入

npm i fabric -S

我用的是全局引入方式,视情况调整 

import fabric from 'fabric';
Vue.use(fabric);

先放一个fabric.js API地址☞Api | Fabric中文文档 (gitee.io) 

2. fabric组件的使用

定义容器id=canvas,注意宽高

  <div class="maintenancePlanAdd">
    <div class="panel-body">
      <div class="demo">
        <canvas id="canvas" :width="width" :height="height" />
        <div class="draw-btn-group" v-show="!readstate">
          <div>
            <el-button class="el-icon-upload" size="mini" type="primary"
                       style="width: 80px !important;" @click="uploadImgConfirm"
            >
              图片上传
            </el-button>
            <el-button size="mini" type="danger" icon="el-icon-delete" @click="clean">
              清除
            </el-button>
          </div>
          <div>
            <el-button
              v-show="bgImgSrc !== ''"
              v-for="(item, index) in alarmLevel"
              :key="index"
              :style="{background:colorGrounp[index]}"
              size="mini"
              @click="drawPolygon(index)"
            >{{ item }}
            </el-button>
          </div>
        </div>
      </div>
    </div>
    <img id="expImg" :src="bgImgSrc">
    <img id="img" :src="bgImgSrc">
    <input v-show="false" type="file" @change="uploadImgChange" id="imgInput" accept="image/*">
    <p class="tip-title" v-show="bgImgSrc === ''">上传的图片可以进行拖拽调整大小和方向</p>
  </div>

3. 属性相关设置

累了,不想写了

 data() {
    return {
      bgImgFlag: true,
      bgImgSrc: '',
      imgFile: {},
      width: 800,
      height: 400,
      alarmLevel: ['一级风险', '二级风险', '三级风险', '四级风险'],
      colorGrounp: ['rgba(51, 164, 255, 1)', 'rgba(255, 200, 89, 1)', 'rgba(255, 160, 89, 1)', 'rgba(196,43, 1, 1)'],
      colorGrounpFill: ['rgba(51, 164, 255, 0.3)', 'rgba(255, 200, 89, 0.3)', 'rgba(255, 160, 89, 0.3)', 'rgba(196,43, 1, 0.3)'],
      canvas: {},
      mouseFrom: {},
      mouseTo: {},
      drawType: '', // 当前绘制图像ROI
      drawWidth: 2, // 笔触宽度
      drawingObject: null, // 当前绘制对象
      moveCount: 1, // 绘制移动计数器
      doDrawing: false, // 绘制状态
      // polygon 相关参数
      polygonMode: false,
      pointArray: [],
      lineArray: [],
      savePointsGroup: [],
      activeShape: false,
      activeLine: '',
      line: {},
      deleteIconURL: require('@/assets/screen/icon-close.png') // 区域标记取消的x号图标
    };
  },

4. 初始化加载

this.canvas = new fabric.Canvas('canvas', {
      skipTargetFind: false, // 当为真时,跳过目标检测。目标检测将返回始终未定义。点击选择将无效
      selectable: false, // 为false时,不能选择对象进行修改
      selection: false // 是否可以多个对象为一组
    });
    this.canvas.selectionColor = 'rgba(0,0,0,0.05)';
    this.canvas.on('mouse:down', this.mousedown);
    this.canvas.on('mouse:move', this.mousemove);
    document.onkeydown = e => {
      // 键盘 delect删除所选元素
      if (e.keyCode == 46) {
        this.deleteObj();
      }
      // ctrl+z 删除最近添加的元素
      if (e.keyCode == 90 && e.ctrlKey) {
        this.canvas.remove(
          this.canvas.getObjects()[this.canvas.getObjects().length - 1]
        );
      }
    };
    this.$nextTick(() => {
      this.loadDraw(); // 回显之前标注过的内容,底图和区域标记内容
    });

4. 方法

methods: {
    // 保存当前画布为png图片
    save() {
      var canvas = document.getElementById('canvas');
      var imgData = canvas.toDataURL('png');
      imgData = imgData.replace('image/png', 'image/octet-stream');
      // 下载后的问题名,可自由指定
      var filename = 'drawingboard_' + (new Date()).getTime() + '.' + 'png';
      this.saveFile(imgData, filename);
    },
    saveFile(data, filename) {
      var save_link = document.createElement('a');
      save_link.href = data;
      save_link.download = filename;
      var event = document.createEvent('MouseEvents');
      event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
      save_link.dispatchEvent(event);
    },
    // 提交绘制内容
    submitDraw() {
      const params = {
        pointInfo: [],
        imgInfo: '',
        img: ''
      };
      this.canvas.toJSON(['alarmLevel', 'isBgImg']).objects.forEach(item => {
        const element = {
          alarmLevel: item.alarmLevel,
          pointInfo: ''
        };
        if (item?.points) {
          element.pointInfo = item.points;
          params.pointInfo.push(element);
        }
        if (item?.isBgImg) {
          params.imgInfo = item;
          params.img = item.src;
          delete params.imgInfo.src;
        }
      });
      this.$emit('saveDraw', params);
    },
    // 清除画布
    clean() {
      this.$confirm('是否清除图片和标记?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.canvas.clear();
        this.bgImgSrc = '';
        this.bgImgFlag = true;
      });
    },
    // 从已渲染的DOM元素加载图片至canvas
    loadExpImg() {
      const imgElement = document.getElementById('expImg');
      imgElement.onload = () => {
        // eslint-disable-next-line new-cap
        new fabric.Image.fromURL(
          imgElement.src,
          img => {
            img.scale(0.3);
            img.set({
              originX: 'center',
              originY: 'center'
            }, { crossOrigin: 'anonymous' });
            img.on('scaling', e => { // 拉伸事件
              const h = img.scaleY;
              const w = img.scaleX;
              if (h !== w || w == h) { // 判断缩放值相等或不相等后执行图片等比缩放
                if (e.e.movementY == -1 || e.e.movementY == 1) {
                  img.scale(h);// 缩放
                } else {
                  img.scale(w);
                }
              }
            });
            img.setCoords();
            img.centeredScaling = true;
            img.centerTransform = true;
            this.canvas.add(img);
            this.canvas.centerObject(img);
            this.canvas.renderAll();
          }, {
            selectable: true,
            hasControls: true,
            centeredScaling: false,
            zIndex: -99,
            isBgImg: true
          });
      };
    },
    // 上传确认
    uploadImgConfirm() {
      if (this.bgImgSrc !== '') {
        this.$confirm('是否重新上传标记?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          this.canvas.clear();
          this.bgImgFlag = true;
          document.getElementById('imgInput').click();
        });
      } else {
        document.getElementById('imgInput').click();
      }
    },
    // 从文件加载图片至canvas
    uploadImgChange() {
      // 获取文件
      var eleImportInput = document.getElementById('imgInput');
      this.imgFile = eleImportInput.files[0];
      var imgTitle = '';
      // 从reader中获取选择文件的src
      if (/\.(jpe?g|png|gif)$/i.test(this.imgFile.name)) {
        var reader = new FileReader();
        var _this = this;
        reader.addEventListener(
          'load',
          function() {
            imgTitle = _this.imgFile.name;
            _this.bgImgSrc = this.result;
          },
          false
        );
        reader.readAsDataURL(this.imgFile);
      }
      this.loadExpImg();
    },
    // 鼠标按下时触发
    mousedown(e) {
      if (undefined === e) return;
      // 记录鼠标按下时的坐标
      var xy = e.pointer || this.transformMouse(e.e.offsetX, e.e.offsetY);
      this.mouseFrom.x = xy.x;
      this.mouseFrom.y = xy.y;
      this.doDrawing = true;

      this.canvas.skipTargetFind = false;
      try {
        // 此段为判断是否闭合多边形,点击红点时闭合多边形
        if (this.pointArray.length > 1) {
          // e.target.id == this.pointArray[0].id 表示点击了初始红点
          if (e.target && e.target.id == this.pointArray[0].id) {
            this.generatePolygon();
            return;
          }
        }
        // 未点击红点则继续作画
        if (this.polygonMode && this.pointArray.length < 4) {
          this.addPoint(e);
        } else if (this.polygonMode && this.pointArray.length > 0) {
          this.$message.warning('最多设置四个点');
        }
      } catch (error) {
        console.log(error);
      }
    },
    // 鼠标松开执行
    mouseup(e) {
      if (undefined === e) return;
      var xy = e.pointer || this.transformMouse(e.e.offsetX, e.e.offsetY);
      this.mouseTo.x = xy.x;
      this.mouseTo.y = xy.y;
      this.drawingObject = null;
      this.moveCount = 1;
    },
    // 鼠标移动过程中已经完成了绘制
    mousemove(e) {
      if (undefined === e) return;
      if (this.moveCount % 2 && !this.doDrawing) {
        // 减少绘制频率
        return;
      }
      this.moveCount++;
      var xy = e.pointer || this.transformMouse(e.e.offsetX, e.e.offsetY);
      this.mouseTo.x = xy.x;
      this.mouseTo.y = xy.y;
      if (this.activeLine && this.activeLine.class == 'line') {
        var pointer = this.canvas.getPointer(e.e);
        this.activeLine.set({
          x2: pointer.x,
          y2: pointer.y
        });
        var points = this.activeShape.get('points');
        points[this.pointArray.length] = {
          x: pointer.x,
          y: pointer.y,
          zIndex: 1
        };
        this.activeShape.set({
          points: points
        });
        this.canvas.renderAll();
      }
      this.canvas.renderAll();
    },
    deleteObj() {
      this.canvas.getActiveObjects().map(item => {
        this.canvas.remove(item);
      });
    },
    transformMouse(mouseX, mouseY) {
      return {
        x: mouseX / 1,
        y: mouseY / 1
      };
    },
    // 绘制多边形开始
    drawPolygon(data) {
      if (this.bgImgFlag) {
        this.canvas.getObjects().forEach(obj => {
          if (obj.isBgImg) {
            obj.hasControls = false;
            obj.selectable = false;
            obj.evented = false;
          }
        });
        this.bgImgFlag = false;
        this.canvas.renderAll();
      }
      this.drawType = data;
      this.polygonMode = true;
      this.pointArray = []; // 顶点集合
      this.lineArray = []; // 线集合
      this.canvas.isDrawingMode = false;
    },
    addPoint(e) {
      var random = Math.floor(Math.random() * 10000);
      var id = new Date().getTime() + random;
      var circle = new fabric.Circle({
        radius: 5,
        fill: '#ffffff',
        stroke: '#333333',
        strokeWidth: 0.5,
        left: (e.pointer.x || e.e.layerX) / this.canvas.getZoom(),
        top: (e.pointer.y || e.e.layerY) / this.canvas.getZoom(),
        selectable: false,
        hasBorders: false,
        hasControls: false,
        originX: 'center',
        originY: 'center',
        id: id,
        objectCaching: false
      });
      if (this.pointArray.length == 0) {
        circle.set({
          fill: this.colorGrounp[this.drawType]
        });
      }
      var points = [
        (e.pointer.x || e.e.layerX) / this.canvas.getZoom(),
        (e.pointer.y || e.e.layerY) / this.canvas.getZoom(),
        (e.pointer.x || e.e.layerX) / this.canvas.getZoom(),
        (e.pointer.y || e.e.layerY) / this.canvas.getZoom()
      ];
      this.line = new fabric.Line(points, {
        strokeWidth: 2,
        fill: this.colorGrounp[this.drawType],
        stroke: this.colorGrounp[this.drawType],
        class: 'line',
        originX: 'center',
        originY: 'center',
        selectable: false,
        hasBorders: false,
        hasControls: false,
        evented: false,
        objectCaching: false
      });
      if (this.activeShape) {
        var pos = this.canvas.getPointer(e.e);
        var points = this.activeShape.get('points');
        points.push({
          x: pos.x,
          y: pos.y
        });
        var polygon = new fabric.Polygon(points, {
          stroke: '#333333',
          strokeWidth: 1,
          fill: this.colorGrounpFill[this.drawType],
          opacity: 0.3,
          selectable: false,
          hasBorders: false,
          hasControls: false,
          evented: false,
          objectCaching: false
        });
        this.canvas.remove(this.activeShape);
        this.canvas.add(polygon);
        this.activeShape = polygon;
        this.canvas.renderAll();
      } else {
        var polyPoint = [
          {
            x: (e.pointer.x || e.e.layerX) / this.canvas.getZoom(),
            y: (e.pointer.y || e.e.layerY) / this.canvas.getZoom()
          }
        ];
        var polygon = new fabric.Polygon(polyPoint, {
          stroke: '#333333',
          strokeWidth: 1,
          fill: '#cccccc',
          opacity: 0.3,
          selectable: false,
          hasBorders: false,
          hasControls: false,
          evented: false,
          objectCaching: false
        });
        this.activeShape = polygon;
        this.canvas.add(polygon);
      }
      this.activeLine = this.line;
      this.pointArray.push(circle);
      this.lineArray.push(this.line);
      this.canvas.add(this.line);
      this.canvas.add(circle);
    },
    generatePolygon() {
      var points = [];
      this.pointArray.map((point, index) => {
        points.push({
          x: point.left,
          y: point.top
        });
        this.canvas.remove(point);
      });
      this.lineArray.map((line, index) => {
        this.canvas.remove(line);
      });
      this.canvas.remove(this.activeShape).remove(this.activeLine);
      var polygon = new fabric.Polygon(points, {
        stroke: this.colorGrounp[this.drawType],
        strokeWidth: this.drawWidth,
        fill: this.colorGrounpFill[this.drawType],
        opacity: 1,
        selectable: false,
        hasBorders: false,
        hasControls: false,
        alarmLevel: this.drawType
      });
      let max = 0;
      for (let i = 1; i < this.canvas._objects.length; i++) {
        if (this.canvas._objects[i].index > max) max = this.canvas._objects[i].index;
      }
      polygon.index = max + 1;
      this.canvas.add(polygon);
      this.activeLine = null;
      this.activeShape = null;
      this.polygonMode = false;
      this.doDrawing = false;
      // this.drawType = null;
      fabric.Image.fromURL(this.deleteIconURL, this.deletecallback);
    },
    // 从画布中删除当前选中的对象
    deleteObject() {
      const activeObject = this.canvas.getActiveObject();
      if (activeObject) {
        this.canvas._objects.forEach(item => {
          if (item.index === activeObject.index) {
            this.canvas.remove(item);
          }
        });
        this.canvas.remove(activeObject);
        this.canvas.renderAll();
      }
    },
    // 渲染删除按钮
    async deletecallback(img) {
      const self = this;
      let max = 0;
      for (let i = 1; i < this.canvas._objects.length; i++) {
        if (this.canvas._objects[i].index > max) max = this.canvas._objects[i].index;
      }
      img.index = max;
      const oImg = await img.set({
        left: this.pointArray[0].left - 20,
        top: this.pointArray[0].top - 20,
        width: 40,
        height: 40,
        angle: 0
      }).scale(0.8);
      this.canvas.add(oImg).renderAll();
      this.canvas.setActiveObject(oImg);
      oImg.on('mousedown', function() {
        self.deleteObject();
      });
    },
    // 回显详情信息
    loadDraw() {
      const self = this;
      if (self.drawinfo.id === '') return;
      const pointGroup = JSON.parse(self.drawinfo.pointInfo);
      const imgInfo = JSON.parse(self.drawinfo.imgInfo);
      self.bgImgSrc = self.drawinfo.img;
      imgInfo.src = self.drawinfo.img;
      // 1、加载底图
      fabric.util.enlivenObjects([imgInfo], objects => {
        objects.forEach(o => {
          o.selectable = false;
          o.hasControls = false;
          o.centeredScaling = false;
          this.canvas.add(o);
        });
        // 2、处理多边形绘制回显操作
        pointGroup.forEach(async (item, index) => {
          if (item.pointInfo !== '') {
            const polygon = new fabric.Polygon(item.pointInfo, {
              stroke: self.colorGrounp[item.alarmLevel],
              strokeWidth: self.drawWidth,
              fill: self.colorGrounpFill[item.alarmLevel],
              opacity: 1,
              selectable: false,
              hasBorders: false,
              hasControls: false,
              alarmLevel: item.alarmLevel
            });
            polygon.index = index;
            self.canvas.add(polygon);
            self.activeLine = null;
            self.activeShape = null;
            self.polygonMode = false;
            self.doDrawing = false;
            if (!self.readstate) {
              fabric.Image.fromURL(self.deleteIconURL, async img => {
                const _self = this;
                img.index = index;
                const oImg = await img.set({
                  left: item.pointInfo[0].x - 20,
                  top: item.pointInfo[0].y - 20,
                  width: 40,
                  height: 40,
                  angle: 0
                }).scale(0.8);
                this.canvas.add(oImg);
                oImg.on('mousedown', function() {
                  _self.deleteObject();
                });
              });
            }
          }
        });
      });
      self.canvas.renderAll();
    }
  }

5. 全代码

累了累了,开始摆烂,以后再调整,直接放全代码吧

<template>
  <div class="maintenancePlanAdd">
    <div class="panel-body">
      <div class="demo">
        <canvas id="canvas" :width="width" :height="height" />
        <div class="draw-btn-group" v-show="!readstate">
          <div>
            <el-button class="el-icon-upload" size="mini" type="primary"
                       style="width: 80px !important;" @click="uploadImgConfirm"
            >
              图片上传
            </el-button>
            <el-button size="mini" type="danger" icon="el-icon-delete" @click="clean">
              清除
            </el-button>
          </div>
          <div>
            <el-button
              v-show="bgImgSrc !== ''"
              v-for="(item, index) in alarmLevel"
              :key="index"
              :style="{background:colorGrounp[index]}"
              size="mini"
              @click="drawPolygon(index)"
            >{{ item }}
            </el-button>
          </div>
        </div>
      </div>
    </div>
    <img id="expImg" :src="bgImgSrc">
    <img id="img" :src="bgImgSrc">
    <input v-show="false" type="file" @change="uploadImgChange" id="imgInput" accept="image/*">
    <p class="tip-title" v-show="bgImgSrc === ''">上传的图片可以进行拖拽调整大小和方向</p>
  </div>
</template>
<script>
export default {
  props: ['readstate', 'drawinfo'],
  data() {
    return {
      bgImgFlag: true,
      bgImgSrc: '',
      imgFile: {},
      width: 800,
      height: 400,
      alarmLevel: ['一级风险', '二级风险', '三级风险', '四级风险'],
      colorGrounp: ['rgba(51, 164, 255, 1)', 'rgba(255, 200, 89, 1)', 'rgba(255, 160, 89, 1)', 'rgba(196,43, 1, 1)'],
      colorGrounpFill: ['rgba(51, 164, 255, 0.3)', 'rgba(255, 200, 89, 0.3)', 'rgba(255, 160, 89, 0.3)', 'rgba(196,43, 1, 0.3)'],
      canvas: {},
      mouseFrom: {},
      mouseTo: {},
      drawType: '', // 当前绘制图像ROI
      drawWidth: 2, // 笔触宽度
      drawingObject: null, // 当前绘制对象
      moveCount: 1, // 绘制移动计数器
      doDrawing: false, // 绘制状态
      // polygon 相关参数
      polygonMode: false,
      pointArray: [],
      lineArray: [],
      savePointsGroup: [],
      activeShape: false,
      activeLine: '',
      line: {},
      deleteIconURL: require('@/assets/screen/icon-close.png')
    };
  },
  watch: {
    drawinfo: {
      handler(n) {
        this.drawinfo = n;
        this.$nextTick(() => {
          this.loadDraw();
        });
      },
      deep: true
    },
    readstate: {
      handler(n) {
        this.readstate = n;
      },
      deep: true
    },
    width() {
      this.canvas.setWidth(this.width);
    },
    height() {
      this.canvas.setHeight(this.height);
    }
  },
  mounted() {
    this.canvas = new fabric.Canvas('canvas', {
      skipTargetFind: false, // 当为真时,跳过目标检测。目标检测将返回始终未定义。点击选择将无效
      selectable: false, // 为false时,不能选择对象进行修改
      selection: false // 是否可以多个对象为一组
    });
    this.canvas.selectionColor = 'rgba(0,0,0,0.05)';
    this.canvas.on('mouse:down', this.mousedown);
    this.canvas.on('mouse:move', this.mousemove);
    document.onkeydown = e => {
      // 键盘 delect删除所选元素
      if (e.keyCode == 46) {
        this.deleteObj();
      }
      // ctrl+z 删除最近添加的元素
      if (e.keyCode == 90 && e.ctrlKey) {
        this.canvas.remove(
          this.canvas.getObjects()[this.canvas.getObjects().length - 1]
        );
      }
    };
    this.$nextTick(() => {
      this.loadDraw();
    });
  },
  methods: {
    // 保存当前画布为png图片
    save() {
      var canvas = document.getElementById('canvas');
      var imgData = canvas.toDataURL('png');
      imgData = imgData.replace('image/png', 'image/octet-stream');
      // 下载后的问题名,可自由指定
      var filename = 'drawingboard_' + (new Date()).getTime() + '.' + 'png';
      this.saveFile(imgData, filename);
    },
    saveFile(data, filename) {
      var save_link = document.createElement('a');
      save_link.href = data;
      save_link.download = filename;
      var event = document.createEvent('MouseEvents');
      event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
      save_link.dispatchEvent(event);
    },
    // 提交绘制内容
    submitDraw() {
      const params = {
        pointInfo: [],
        imgInfo: '',
        img: ''
      };
      this.canvas.toJSON(['alarmLevel', 'isBgImg']).objects.forEach(item => {
        const element = {
          alarmLevel: item.alarmLevel,
          pointInfo: ''
        };
        if (item?.points) {
          element.pointInfo = item.points;
          params.pointInfo.push(element);
        }
        if (item?.isBgImg) {
          params.imgInfo = item;
          params.img = item.src;
          delete params.imgInfo.src;
        }
      });
      this.$emit('saveDraw', params);
    },
    // 清除画布
    clean() {
      this.$confirm('是否清除图片和标记?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.canvas.clear();
        this.bgImgSrc = '';
        this.bgImgFlag = true;
      });
    },
    // 从已渲染的DOM元素加载图片至canvas
    loadExpImg() {
      const imgElement = document.getElementById('expImg');
      imgElement.onload = () => {
        // eslint-disable-next-line new-cap
        new fabric.Image.fromURL(
          imgElement.src,
          img => {
            img.scale(0.3);
            img.set({
              originX: 'center',
              originY: 'center'
            }, { crossOrigin: 'anonymous' });
            img.on('scaling', e => { // 拉伸事件
              const h = img.scaleY;
              const w = img.scaleX;
              if (h !== w || w == h) { // 判断缩放值相等或不相等后执行图片等比缩放
                if (e.e.movementY == -1 || e.e.movementY == 1) {
                  img.scale(h);// 缩放
                } else {
                  img.scale(w);
                }
              }
            });
            img.setCoords();
            img.centeredScaling = true;
            img.centerTransform = true;
            this.canvas.add(img);
            this.canvas.centerObject(img);
            this.canvas.renderAll();
          }, {
            selectable: true,
            hasControls: true,
            centeredScaling: false,
            zIndex: -99,
            isBgImg: true
          });
      };
    },
    // 上传确认
    uploadImgConfirm() {
      if (this.bgImgSrc !== '') {
        this.$confirm('是否重新上传标记?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          this.canvas.clear();
          this.bgImgFlag = true;
          document.getElementById('imgInput').click();
        });
      } else {
        document.getElementById('imgInput').click();
      }
    },
    // 从文件加载图片至canvas
    uploadImgChange() {
      // 获取文件
      var eleImportInput = document.getElementById('imgInput');
      this.imgFile = eleImportInput.files[0];
      var imgTitle = '';
      // 从reader中获取选择文件的src
      if (/\.(jpe?g|png|gif)$/i.test(this.imgFile.name)) {
        var reader = new FileReader();
        var _this = this;
        reader.addEventListener(
          'load',
          function() {
            imgTitle = _this.imgFile.name;
            _this.bgImgSrc = this.result;
          },
          false
        );
        reader.readAsDataURL(this.imgFile);
      }
      this.loadExpImg();
    },
    // 鼠标按下时触发
    mousedown(e) {
      if (undefined === e) return;
      // 记录鼠标按下时的坐标
      var xy = e.pointer || this.transformMouse(e.e.offsetX, e.e.offsetY);
      this.mouseFrom.x = xy.x;
      this.mouseFrom.y = xy.y;
      this.doDrawing = true;

      this.canvas.skipTargetFind = false;
      try {
        // 此段为判断是否闭合多边形,点击红点时闭合多边形
        if (this.pointArray.length > 1) {
          // e.target.id == this.pointArray[0].id 表示点击了初始红点
          if (e.target && e.target.id == this.pointArray[0].id) {
            this.generatePolygon();
            return;
          }
        }
        // 未点击红点则继续作画
        if (this.polygonMode && this.pointArray.length < 4) {
          this.addPoint(e);
        } else if (this.polygonMode && this.pointArray.length > 0) {
          this.$message.warning('最多设置四个点');
        }
      } catch (error) {
        console.log(error);
      }
    },
    // 鼠标松开执行
    mouseup(e) {
      if (undefined === e) return;
      var xy = e.pointer || this.transformMouse(e.e.offsetX, e.e.offsetY);
      this.mouseTo.x = xy.x;
      this.mouseTo.y = xy.y;
      this.drawingObject = null;
      this.moveCount = 1;
    },
    // 鼠标移动过程中已经完成了绘制
    mousemove(e) {
      if (undefined === e) return;
      if (this.moveCount % 2 && !this.doDrawing) {
        // 减少绘制频率
        return;
      }
      this.moveCount++;
      var xy = e.pointer || this.transformMouse(e.e.offsetX, e.e.offsetY);
      this.mouseTo.x = xy.x;
      this.mouseTo.y = xy.y;
      if (this.activeLine && this.activeLine.class == 'line') {
        var pointer = this.canvas.getPointer(e.e);
        this.activeLine.set({
          x2: pointer.x,
          y2: pointer.y
        });
        var points = this.activeShape.get('points');
        points[this.pointArray.length] = {
          x: pointer.x,
          y: pointer.y,
          zIndex: 1
        };
        this.activeShape.set({
          points: points
        });
        this.canvas.renderAll();
      }
      this.canvas.renderAll();
    },
    deleteObj() {
      this.canvas.getActiveObjects().map(item => {
        this.canvas.remove(item);
      });
    },
    transformMouse(mouseX, mouseY) {
      return {
        x: mouseX / 1,
        y: mouseY / 1
      };
    },
    // 绘制多边形开始
    drawPolygon(data) {
      if (this.bgImgFlag) {
        this.canvas.getObjects().forEach(obj => {
          if (obj.isBgImg) {
            obj.hasControls = false;
            obj.selectable = false;
            obj.evented = false;
          }
        });
        this.bgImgFlag = false;
        this.canvas.renderAll();
      }
      this.drawType = data;
      this.polygonMode = true;
      this.pointArray = []; // 顶点集合
      this.lineArray = []; // 线集合
      this.canvas.isDrawingMode = false;
    },
    addPoint(e) {
      var random = Math.floor(Math.random() * 10000);
      var id = new Date().getTime() + random;
      var circle = new fabric.Circle({
        radius: 5,
        fill: '#ffffff',
        stroke: '#333333',
        strokeWidth: 0.5,
        left: (e.pointer.x || e.e.layerX) / this.canvas.getZoom(),
        top: (e.pointer.y || e.e.layerY) / this.canvas.getZoom(),
        selectable: false,
        hasBorders: false,
        hasControls: false,
        originX: 'center',
        originY: 'center',
        id: id,
        objectCaching: false
      });
      if (this.pointArray.length == 0) {
        circle.set({
          fill: this.colorGrounp[this.drawType]
        });
      }
      var points = [
        (e.pointer.x || e.e.layerX) / this.canvas.getZoom(),
        (e.pointer.y || e.e.layerY) / this.canvas.getZoom(),
        (e.pointer.x || e.e.layerX) / this.canvas.getZoom(),
        (e.pointer.y || e.e.layerY) / this.canvas.getZoom()
      ];
      this.line = new fabric.Line(points, {
        strokeWidth: 2,
        fill: this.colorGrounp[this.drawType],
        stroke: this.colorGrounp[this.drawType],
        class: 'line',
        originX: 'center',
        originY: 'center',
        selectable: false,
        hasBorders: false,
        hasControls: false,
        evented: false,
        objectCaching: false
      });
      if (this.activeShape) {
        var pos = this.canvas.getPointer(e.e);
        var points = this.activeShape.get('points');
        points.push({
          x: pos.x,
          y: pos.y
        });
        var polygon = new fabric.Polygon(points, {
          stroke: '#333333',
          strokeWidth: 1,
          fill: this.colorGrounpFill[this.drawType],
          opacity: 0.3,
          selectable: false,
          hasBorders: false,
          hasControls: false,
          evented: false,
          objectCaching: false
        });
        this.canvas.remove(this.activeShape);
        this.canvas.add(polygon);
        this.activeShape = polygon;
        this.canvas.renderAll();
      } else {
        var polyPoint = [
          {
            x: (e.pointer.x || e.e.layerX) / this.canvas.getZoom(),
            y: (e.pointer.y || e.e.layerY) / this.canvas.getZoom()
          }
        ];
        var polygon = new fabric.Polygon(polyPoint, {
          stroke: '#333333',
          strokeWidth: 1,
          fill: '#cccccc',
          opacity: 0.3,
          selectable: false,
          hasBorders: false,
          hasControls: false,
          evented: false,
          objectCaching: false
        });
        this.activeShape = polygon;
        this.canvas.add(polygon);
      }
      this.activeLine = this.line;
      this.pointArray.push(circle);
      this.lineArray.push(this.line);
      this.canvas.add(this.line);
      this.canvas.add(circle);
    },
    generatePolygon() {
      var points = [];
      this.pointArray.map((point, index) => {
        points.push({
          x: point.left,
          y: point.top
        });
        this.canvas.remove(point);
      });
      this.lineArray.map((line, index) => {
        this.canvas.remove(line);
      });
      this.canvas.remove(this.activeShape).remove(this.activeLine);
      var polygon = new fabric.Polygon(points, {
        stroke: this.colorGrounp[this.drawType],
        strokeWidth: this.drawWidth,
        fill: this.colorGrounpFill[this.drawType],
        opacity: 1,
        selectable: false,
        hasBorders: false,
        hasControls: false,
        alarmLevel: this.drawType
      });
      let max = 0;
      for (let i = 1; i < this.canvas._objects.length; i++) {
        if (this.canvas._objects[i].index > max) max = this.canvas._objects[i].index;
      }
      polygon.index = max + 1;
      this.canvas.add(polygon);
      this.activeLine = null;
      this.activeShape = null;
      this.polygonMode = false;
      this.doDrawing = false;
      // this.drawType = null;
      fabric.Image.fromURL(this.deleteIconURL, this.deletecallback);
    },
    // 从画布中删除当前选中的对象
    deleteObject() {
      const activeObject = this.canvas.getActiveObject();
      if (activeObject) {
        this.canvas._objects.forEach(item => {
          if (item.index === activeObject.index) {
            this.canvas.remove(item);
          }
        });
        this.canvas.remove(activeObject);
        this.canvas.renderAll();
      }
    },
    // 渲染删除按钮
    async deletecallback(img) {
      const self = this;
      let max = 0;
      for (let i = 1; i < this.canvas._objects.length; i++) {
        if (this.canvas._objects[i].index > max) max = this.canvas._objects[i].index;
      }
      img.index = max;
      const oImg = await img.set({
        left: this.pointArray[0].left - 20,
        top: this.pointArray[0].top - 20,
        width: 40,
        height: 40,
        angle: 0
      }).scale(0.8);
      this.canvas.add(oImg).renderAll();
      this.canvas.setActiveObject(oImg);
      oImg.on('mousedown', function() {
        self.deleteObject();
      });
    },
    // 回显详情信息
    loadDraw() {
      const self = this;
      if (self.drawinfo.id === '') return;
      const pointGroup = JSON.parse(self.drawinfo.pointInfo);
      const imgInfo = JSON.parse(self.drawinfo.imgInfo);
      self.bgImgSrc = self.drawinfo.img;
      imgInfo.src = self.drawinfo.img;
      // 1、加载底图
      fabric.util.enlivenObjects([imgInfo], objects => {
        objects.forEach(o => {
          o.selectable = false;
          o.hasControls = false;
          o.centeredScaling = false;
          this.canvas.add(o);
        });
        // 2、处理多边形绘制回显操作
        pointGroup.forEach(async (item, index) => {
          if (item.pointInfo !== '') {
            const polygon = new fabric.Polygon(item.pointInfo, {
              stroke: self.colorGrounp[item.alarmLevel],
              strokeWidth: self.drawWidth,
              fill: self.colorGrounpFill[item.alarmLevel],
              opacity: 1,
              selectable: false,
              hasBorders: false,
              hasControls: false,
              alarmLevel: item.alarmLevel
            });
            polygon.index = index;
            self.canvas.add(polygon);
            self.activeLine = null;
            self.activeShape = null;
            self.polygonMode = false;
            self.doDrawing = false;
            if (!self.readstate) {
              fabric.Image.fromURL(self.deleteIconURL, async img => {
                const _self = this;
                img.index = index;
                const oImg = await img.set({
                  left: item.pointInfo[0].x - 20,
                  top: item.pointInfo[0].y - 20,
                  width: 40,
                  height: 40,
                  angle: 0
                }).scale(0.8);
                this.canvas.add(oImg);
                oImg.on('mousedown', function() {
                  _self.deleteObject();
                });
              });
            }
          }
        });
      });
      self.canvas.renderAll();
    }
  }
};
</script>

<style lang="scss" scoped>
.el-container {
  flex-direction: column;
}

img {
  display: none;
}

.demo {
  display: flex;
  flex-direction: column;
  align-items: center;
}

canvas {
  border: 1px dashed #2695F9;
}

.draw-btn-group {
  width: 100%;
  margin-top: 10px;
  display: flex;
  align-items: center;
  justify-content: space-between;

  .el-button {
    color: #ffffff;
  }
}

.tip-title {
  top: 40%;
  left: 33%;
  position: absolute;
  font-size: 16px;
  color: #C2C7CC;
  margin: 0;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/336480.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Flink(十三)【Flink SQL(上)SqlClient、DDL、查询】

前言 最近在假期实训&#xff0c;但是实在水的不行&#xff0c;三天要学完SSM&#xff0c;实在一言难尽&#xff0c;浪费那时间干什么呢。SSM 之前学了一半&#xff0c;等后面忙完了&#xff0c;再去好好重学一遍&#xff0c;毕竟这玩意真是面试必会的东西。 今天开始学习 Flin…

“GPC爬虫池有用吗?

作为光算科技的独有技术&#xff0c;在深入研究谷歌爬虫推出的一种吸引谷歌爬虫的手段 要知道GPC爬虫池是否有用&#xff0c;就要知道谷歌爬虫这一概念&#xff0c;谷歌作为一个搜索引擎&#xff0c;里面有成百上千亿个网站&#xff0c;对于里面的网站内容&#xff0c;自然不可…

虚拟机安装宝塔的坑

问题&#xff1a; 在虚拟机中centos7和centos8中安装宝塔之后&#xff0c;无法访问面板。 解决&#xff1a; 1.先关闭防火墙&#xff08;如果本机能够ping通相关端口&#xff0c;则不用关闭防火墙&#xff09; 2.最新的宝塔会自动开启ssl协议&#xff0c;需要手动关闭。…

PostgreSQL 是不是大小写敏感

如果你踩过 MySQL 的大坑的话就知道&#xff1a;MySQL 在 Windows 下不区分大小写&#xff0c;但在 Linux 下默认是区分大小写。 如果你稍加不注意就会出现在本机开发的程序运行一切正常&#xff0c;发布到服务器行就出现表名找不到的问题。 这是我们前一个项目遇到的巨大问题…

【力扣4行代码解题】572另一棵树的子树 | C++

总结&#xff1a;本题可以使用递归和迭代法&#xff0c;但平时还是建议两种方法都掌握&#xff0c;感兴趣的同学可以看看原题。 文章目录 1 题目2 知识点3 代码及解释 1 题目 力扣链接 > 572.另一棵树的子树 给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 sub…

探索图像检索:从理论到实战的应用

目录 一、引言二、图像检索技术概述图像检索的基本概念图像检索与文本检索的区别特征提取技术相似度计算索引技术 三、图像检索技术代码示例图像特征提取示例相似度计算索引技术 四、图像搜索流程架构数据采集与预处理特征提取相似度计算与排名结果呈现与优化 五、实际应用图像…

基于Java+SSM志愿者服务管理系统详细设计和实现【附源码】

基于JavaSSM志愿者服务管理系统详细设计和实现【附源码】 &#x1f345; 作者主页 央顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取联系方式 承接各种定制系…

线上SQL超时场景分析-MySQL超时之间隙锁

前言 之前遇到过一个由MySQL间隙锁引发线上sql执行超时的场景&#xff0c;记录一下。 背景说明 分布式事务消息表&#xff1a;业务上使用消息表的方式&#xff0c;依赖本地事务&#xff0c;实现了一套分布式事务方案 消息表名&#xff1a;mq_messages 数据量&#xff1a;3000多…

[java基础揉碎]基本数据类型转换

介绍 当java程序在进行赋值或者运算时&#xff0c;精度小的类型自动转换为精度大的数据类型&#xff0c; 这个就是自动类型转换。 数据类型按精度&#xff08;容量&#xff09;大小排序为: 自动类型转换注意和细节 1.有多种类型的数据混合运算时&#xff0c;系统首先自动…

【Linux的权限命令详解】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 shell命令以及运行原理 Linux权限的概念 Linux权限管理 一、什么是权限&#xff1f; 二、权限的本质 三、Linux中的用户 四、linux中文件的权限 4.1、文件访问…

【成本价特惠】招募证书代理:工信部、PMP、阿里云、华为等认证,机会难得!

扫码和我联系 亲爱的读者朋友们&#xff0c; 今天&#xff0c;我想和大家分享一个难得的机会。我们目前正在积极招募各类证书的代理&#xff0c;包括工信部的证书、PMP&#xff08;项目管理专业人士&#xff09;证书、阿里云证书、华为证书、OCP 证书、CFA 证书等。这些证书在…

C++入门学习(七)整型

整型就是整数类型的数据&#xff08;-1&#xff0c;0&#xff0c;1等等&#xff09; 数据类型占用空间取值范围short(短整型)2字节 (-2^15 ~ 2^15-1) 32768~32767 int(整型)4字节(-2^31 ~ 2^31-1)long(长整形) Windows为4字节, Linux为4字节(32位), 8字节(64位) (-2^31 ~ 2^31…

LiteAD对接FusionCompute

2、FA发放云桌面并与FC对接 &#xff08;1&#xff09;创建虚拟机模板 &#xff08;2&#xff09;创建虚拟机命名规则 &#xff08;3&#xff09;创建虚拟机组 &#xff08;4&#xff09;创建桌面组 &#xff08;5&#xff09;创建域用户和组&#xff08;就相当于在Microsoft …

基于SpringBoot Vue家政服务预约平台系统

大家好✌&#xff01;我是Dwzun。很高兴你能来阅读我&#xff0c;我会陆续更新Java后端、前端、数据库、项目案例等相关知识点总结&#xff0c;还为大家分享优质的实战项目&#xff0c;本人在Java项目开发领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#x…

【linux驱动】详细剖析第一个hello word驱动程序

文章目录 驱动程序的框架驱动程序的使用示例 驱动程序的框架 Linux 驱动的基本框架主要由模块加载函数&#xff0c;模块卸载函数&#xff0c;模块许可证声明&#xff0c;模块参数&#xff0c;模块导出符号&#xff0c;模块作者信息等几部分组成&#xff0c;其中模块参数&#…

C++——vector的使用及其模拟实现

vector的使用及其模拟实现 文章目录 vector的使用及其模拟实现1. vector的使用1.1 构造函数construct1.2 获取当前存储的数据个数size()和最大容量capacity()1.3 访问1.3.1 operator[]运算符重载1.3.2 迭代器访问1.3.3 范围for 1.4 容量相关reserve()和resize()1.5 增&#xff…

【趣味题-04】20240120百鸡百钱(遍历循环排除,类似加减法)

背景需求 题目 5&#xff1a;百鸡百钱 作者&#xff1a;阿夏 时间&#xff1a;2024年1月20日百鸡百钱 公鸡每只 5 元&#xff0c;母鸡每只 3 元&#xff0c;小鸡 3 只一元&#xff0c; 现要求用 100 元钱买 100 只鸡(三种类型的鸡都要买)&#xff0c; 问公鸡、母鸡、小鸡各买几…

Oracle 经典练习题 50 题

文章目录 一 CreateTable二 练习题1 查询"01"课程比"02"课程成绩高的学生的信息及课程分数2 查询"01"课程比"02"课程成绩低的学生的信息及课程分数3 查询平均成绩大于等于60分的同学的学生编号和学生姓名和平均成绩4 查询平均成绩小于…

“加密行业迈向上市潮”!全球第二大稳定币发行商,秘密提交IPO申请!

在过去的15年里&#xff0c;虽然加密行业蓬勃发展&#xff0c;但上市公司的数量却相对稀少。然而&#xff0c;随着加密市场在经济走强的背景下出现反弹&#xff0c;今年区块链领域的投资资金和潜在的IPO似乎均有望迎来转机。 高盛预测&#xff0c;2024年下半年IPO活动将更加活跃…

Red Hat Enterprise Linux 8.9 安装图解

引导和开始安装 选择倒计时结束前&#xff0c;通过键盘上下键选择下图框选项&#xff0c;启动图形化安装过程。需要注意的不同主板默认或者自行配置的固件类型不一致&#xff0c;引导界面有所不同。也就是说使用UEFI和BIOS的安装引导界面是不同的&#xff0c;如图所示。若手动调…