canvas绘制可视化标注

npm install fabric@4.6.0 --save

  <div @contextmenu.prevent>
    <div class="toolbar">
      <input type="file" @change="handleImageUpload"/>
      <el-color-picker
        v-model="selectedColor"
        @change="onColorChange"
        show-alpha
        style="margin-left: 10px;"
      ></el-color-picker>
      <button @click="drawMode = 'line'; enableLineDrawMode()">画线</button>
      <button @click="addCircle">圆形</button>
      <button @click="addText">文字</button>
    </div>
    <div
      v-if="contextMenu.visible"
      :style="contextMenu.style"
      class="context-menu"
      @click="deleteSelectedObject"
    >
      删除
    </div>
    <canvas ref="canvas" width="800" height="600" style="border:1px solid #ccc;"></canvas>
  <el-button @click="saveHandle">保存</el-button>
  </div>
</template>

<script>
import {fabric} from 'fabric';

export default {
  name: 'DrawBoard',
  data() {
    return {
      canvas: null,
      drawMode: null,
      isDrawingLine: false,
      currentLine: null,
      selectedColor: '#000000', // 默认黑色
      contextMenu: {
        visible: false,
        style: {
          top: '0px',
          left: '0px'
        },
        target: null  // 当前右键选中的元素
      }
    };
  },
  mounted() {

    this.canvas = new fabric.Canvas(this.$refs.canvas, {
      fireRightClick: true, // 启用右键,button的数字为3
      stopContextMenu: true, // 禁止默认右键菜单
      selection: true,
      preserveObjectStacking: true
    });
    this.canvas.on('mouse:down', (opt) => {
      console.log('mouse:down', opt);
      if (opt.e.button === 2) { // 鼠标右键
        const target = opt.target;
        if (target) {
          this.contextMenu.target = target;

          // 显示右键菜单
          this.contextMenu.visible = true;
          this.contextMenu.style = {
            top: `${opt.e.clientY}px`,
            left: `${opt.e.clientX}px`,
            position: 'absolute'
          };
        } else {
          this.contextMenu.visible = false;
        }

        // 阻止系统默认右键菜单
        opt.e.preventDefault();
        opt.e.stopPropagation();
      } else {
        this.contextMenu.visible = false;
      }
    });
    this.reductionHandle()
  },
  methods: {
    reductionHandle(){
      let jsonString = '{"version":"4.6.0","objects":[{"type":"circle","version":"4.6.0","originX":"left","originY":"top","left":97,"top":106,"width":80,"height":80,"fill":"#000000","stroke":"#000","strokeWidth":2,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":3.49,"scaleY":3.49,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"radius":40,"startAngle":0,"endAngle":6.283185307179586},{"type":"textbox","version":"4.6.0","originX":"left","originY":"top","left":200,"top":200,"width":120,"height":27.12,"fill":"#000000","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"fontFamily":"Times New Roman","fontWeight":"normal","fontSize":24,"text":"请输入文字","underline":false,"overline":false,"linethrough":false,"textAlign":"left","fontStyle":"normal","lineHeight":1.16,"textBackgroundColor":"","charSpacing":0,"styles":{},"direction":"ltr","path":null,"pathStartOffset":0,"pathSide":"left","minWidth":20,"splitByGrapheme":false},{"type":"textbox","version":"4.6.0","originX":"left","originY":"top","left":474,"top":131,"width":120,"height":27.12,"fill":"#000000","stroke":null,"strokeWidth":1,"strokeDashArray":null,"strokeLineCap":"butt","strokeDashOffset":0,"strokeLineJoin":"miter","strokeUniform":false,"strokeMiterLimit":4,"scaleX":1,"scaleY":1,"angle":0,"flipX":false,"flipY":false,"opacity":1,"shadow":null,"visible":true,"backgroundColor":"","fillRule":"nonzero","paintFirst":"fill","globalCompositeOperation":"source-over","skewX":0,"skewY":0,"fontFamily":"Times New Roman","fontWeight":"normal","fontSize":24,"text":"测试","underline":false,"overline":false,"linethrough":false,"textAlign":"left","fontStyle":"normal","lineHeight":1.16,"textBackgroundColor":"","charSpacing":0,"styles":{},"direction":"ltr","path":null,"pathStartOffset":0,"pathSide":"left","minWidth":20,"splitByGrapheme":false}]}'
      this.canvas.loadFromJSON(jsonString, () => {
        this.canvas.renderAll();
      });
    },
    saveHandle() {
      const jsonData = this.canvas.toJSON();
      const jsonString = JSON.stringify(jsonData);
      console.log(jsonString)
      return
// 传给后端保存
      axios.post('/api/save-canvas', {data: jsonString});
    },
    onColorChange(color) {
      const activeObject = this.canvas.getActiveObject();
      if (activeObject) {
        // 判断对象类型,设置颜色属性
        if (activeObject.type === 'text' || activeObject.type === 'textbox') {
          activeObject.set('fill', color);
        } else if (activeObject.type === 'line' || activeObject.stroke) {
          activeObject.set('stroke', color);
        }
        this.canvas.renderAll();
      }
    },
    deleteSelectedObject() {
      if (this.contextMenu.target) {
        this.canvas.remove(this.contextMenu.target);
        this.canvas.renderAll();
        this.contextMenu.visible = false;
        this.contextMenu.target = null;
      }
    },

    /** 上传图片并添加到画布 */
    handleImageUpload(e) {
      const file = e.target.files[0];
      if (!file) return;

      const reader = new FileReader();
      reader.onload = (f) => {
        fabric.Image.fromURL(f.target.result, (img) => {
          img.set({left: 100, top: 100, scaleX: 0.5, scaleY: 0.5});
          this.canvas.add(img);
        });
      };
      reader.readAsDataURL(file);
    },

    /** 添加圆形 */
    addCircle() {
      const circle = new fabric.Circle({
        left: 150,
        top: 150,
        radius: 40,
        fill: this.selectedColor,
        stroke: '#000',
        strokeWidth: 2,
      });
      this.canvas.add(circle);
    },

    /** 添加文字 */
    addText() {
      const text = new fabric.Textbox('请输入文字', {
        left: 200,
        top: 200,
        fontSize: 24,
        fill: this.selectedColor,
        editable: true,
      });
      this.canvas.add(text);
    },

    /** 启用画线模式 */
    enableLineDrawMode() {
      this.canvas.selection = false;
      this.canvas.defaultCursor = 'crosshair';
      this.isDrawingLine = true;

      this.canvas.on('mouse:down', this.startLine);
      this.canvas.on('mouse:move', this.drawLine);
      this.canvas.on('mouse:up', this.endLine);
    },

    startLine(opt) {
      if (!this.isDrawingLine) return;
      const pointer = this.canvas.getPointer(opt.e);
      const points = [pointer.x, pointer.y, pointer.x, pointer.y];

      const line = new fabric.Line(points, {
        strokeWidth: 2,
        stroke: this.selectedColor,
        selectable: false,
        evented: false,
      });

      this.currentLine = line;
      this.canvas.add(line);
    },

    drawLine(opt) {
      if (!this.isDrawingLine || !this.currentLine) return;

      const pointer = this.canvas.getPointer(opt.e);
      this.currentLine.set({x2: pointer.x, y2: pointer.y});
      this.canvas.renderAll();
    },

    endLine() {
      if (!this.isDrawingLine) return;

      this.currentLine.set({selectable: true, evented: true});
      this.currentLine = null;

      this.canvas.off('mouse:down', this.startLine);
      this.canvas.off('mouse:move', this.drawLine);
      this.canvas.off('mouse:up', this.endLine);

      this.canvas.selection = true;
      this.canvas.defaultCursor = 'default';
      this.isDrawingLine = false;
    },
  }
};
</script>

<style scoped>
.toolbar {
  margin-bottom: 10px;
}

button {
  margin-right: 5px;
}

.context-menu {
  position: absolute;
  background: white;
  border: 1px solid #ccc;
  z-index: 9999;
  padding: 5px 10px;
  cursor: pointer;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
</style>
<template>
  <div>
    <div class="toolbar">
      <input type="file" @change="handleImageUpload" />
      <button @click="drawMode = 'line'; enableLineDrawMode()">画线</button>
      <button @click="addCircle">圆形</button>
      <button @click="addText">文字</button>
    </div>
    <canvas ref="canvas" width="800" height="600" style="border:1px solid #ccc;"></canvas>
  </div>
</template>

<script>
import { fabric } from 'fabric';

export default {
  name: 'DrawBoard',
  data() {
    return {
      canvas: null,
      drawMode: null,
      isDrawingLine: false,
      currentLine: null,
    };
  },
  mounted() {
    this.canvas = new fabric.Canvas(this.$refs.canvas, {
      selection: true,
      preserveObjectStacking: true
    });
  },
  methods: {
    /** 上传图片并添加到画布 */
    handleImageUpload(e) {
      const file = e.target.files[0];
      if (!file) return;

      const reader = new FileReader();
      reader.onload = (f) => {
        fabric.Image.fromURL(f.target.result, (img) => {
          img.set({ left: 100, top: 100, scaleX: 0.5, scaleY: 0.5 });
          this.canvas.add(img);
        });
      };
      reader.readAsDataURL(file);
    },

    /** 添加圆形 */
    addCircle() {
      const circle = new fabric.Circle({
        left: 150,
        top: 150,
        radius: 40,
        fill: 'rgba(0,0,255,0.5)',
        stroke: '#000',
        strokeWidth: 2,
      });
      this.canvas.add(circle);
    },

    /** 添加文字 */
    addText() {
      const text = new fabric.Textbox('请输入文字', {
        left: 200,
        top: 200,
        fontSize: 24,
        fill: '#333',
        editable: true,
      });
      this.canvas.add(text);
    },

    /** 启用画线模式 */
    enableLineDrawMode() {
      this.canvas.selection = false;
      this.canvas.defaultCursor = 'crosshair';
      this.isDrawingLine = true;

      this.canvas.on('mouse:down', this.startLine);
      this.canvas.on('mouse:move', this.drawLine);
      this.canvas.on('mouse:up', this.endLine);
    },

    startLine(opt) {
      if (!this.isDrawingLine) return;
      const pointer = this.canvas.getPointer(opt.e);
      const points = [pointer.x, pointer.y, pointer.x, pointer.y];

      const line = new fabric.Line(points, {
        strokeWidth: 2,
        stroke: 'red',
        selectable: false,
        evented: false,
      });

      this.currentLine = line;
      this.canvas.add(line);
    },

    drawLine(opt) {
      if (!this.isDrawingLine || !this.currentLine) return;

      const pointer = this.canvas.getPointer(opt.e);
      this.currentLine.set({ x2: pointer.x, y2: pointer.y });
      this.canvas.renderAll();
    },

    endLine() {
      if (!this.isDrawingLine) return;

      this.currentLine.set({ selectable: true, evented: true });
      this.currentLine = null;

      this.canvas.off('mouse:down', this.startLine);
      this.canvas.off('mouse:move', this.drawLine);
      this.canvas.off('mouse:up', this.endLine);

      this.canvas.selection = true;
      this.canvas.defaultCursor = 'default';
      this.isDrawingLine = false;
    },
  }
};
</script>

<style scoped>
.toolbar {
  margin-bottom: 10px;
}
button {
  margin-right: 5px;
}
</style>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容