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

目录

0. 前言

1. 安装fabric与引入

2. fabric组件的使用

3. 属性相关设置

4. 初始化加载

4. 方法

5. 全代码


0. 前言

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

先放一张效果图吧

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

1. 安装fabric与引入

npm i fabric -S

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

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

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

2. fabric组件的使用

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

  
图片上传 清除
{{ item }}

上传的图片可以进行拖拽调整大小和方向

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. 全代码

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





你可能感兴趣的:(fabric,vue.js)