标注canvas案例

<template>
  <div>
    <canvas
      ref="canvas"
      :width="canvasWidth"
      :height="canvasHeight"
      style="border: 1px solid #ccc"
    ></canvas>
  </div>
</template>

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

export default {
  name: "ImageAnnotator",
  data() {
    return {
      canvas: null,
      canvasWidth: 800,
      canvasHeight: 600,
      isDrawing: false,
      isCtrlPressed: false,
      drawingRect: null,
      startX: 0,
      startY: 0,
      hasMoved: false,
      imageBounds: null,
      boxCount: 0,
      labelMap: new Map(),
    };
  },
  mounted() {
    this.initCanvas();
    this.loadBackgroundImage();
    this.bindKeyboardEvents();
  },
  methods: {
    initCanvas() {
      this.canvas = new fabric.Canvas(this.$refs.canvas, {
        selection: true,
        preserveObjectStacking: true,
      });

      this.canvas.on("mouse:down", (opt) => {
        if (this.isCtrlPressed) {
          this.canvas.isDragging = true;
          this.canvas.selection = false;
          this.canvas.lastPosX = opt.e.clientX;
          this.canvas.lastPosY = opt.e.clientY;
          return;
        }

        if (opt.target) return;

        const pointer = this.canvas.getPointer(opt.e);
        if (!this.isPointInsideImage(pointer)) return;

        this.isDrawing = true;
        this.hasMoved = false;
        this.startX = pointer.x;
        this.startY = pointer.y;
        this.drawingRect = null;
      });

      this.canvas.on("mouse:move", (opt) => {
        const pointer = this.canvas.getPointer(opt.e);

        if (this.canvas.isDragging && this.isCtrlPressed) {
          const e = opt.e;
          const vpt = this.canvas.viewportTransform;
          vpt[4] += e.clientX - this.canvas.lastPosX;
          vpt[5] += e.clientY - this.canvas.lastPosY;
          this.canvas.lastPosX = e.clientX;
          this.canvas.lastPosY = e.clientY;
          this.canvas.requestRenderAll();
          return;
        }

        if (!this.isDrawing) return;

        const dx = pointer.x - this.startX;
        const dy = pointer.y - this.startY;
        const newLeft = dx >= 0 ? this.startX : pointer.x;
        const newTop = dy >= 0 ? this.startY : pointer.y;
        const newRight = newLeft + Math.abs(dx);
        const newBottom = newTop + Math.abs(dy);

        if (!this.isRectInsideImage(newLeft, newTop, newRight, newBottom)) return;

        if (!this.hasMoved && (Math.abs(dx) > 5 || Math.abs(dy) > 5)) {
          this.hasMoved = true;
          this.drawingRect = new fabric.Rect({
            left: newLeft,
            top: newTop,
            width: Math.abs(dx),
            height: Math.abs(dy),
            fill: "rgba(0, 255, 0, 0.3)",
            stroke: "green",
            strokeWidth: 1,
            strokeUniform: true,
            selectable: true,
            hasControls: true,
            hasBorders: true,
            lockUniScaling: false,
            objectCaching: false,
            transparentCorners: false,
          });
          this.canvas.add(this.drawingRect);
        }

        if (this.hasMoved && this.drawingRect) {
          this.drawingRect.set({
            width: Math.abs(dx),
            height: Math.abs(dy),
            left: newLeft,
            top: newTop,
          });
          this.canvas.requestRenderAll();
        }
      });

      this.canvas.on("mouse:up", () => {
        if (this.canvas.isDragging) {
          this.canvas.setViewportTransform(this.canvas.viewportTransform);
        }
        this.canvas.isDragging = false;
        this.canvas.selection = true;

        if (
          this.drawingRect &&
          (this.drawingRect.width < 10 || this.drawingRect.height < 10)
        ) {
          this.canvas.remove(this.drawingRect);
        } else if (this.drawingRect) {
          const rect = this.drawingRect;
          this.boxCount++;

          setTimeout(() => {
            const label = new fabric.Textbox(
              `框 ${this.boxCount}\nvalue:我是哈哈哈哈.哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈..${this.boxCount}`,
              {
                fontSize: 14,
                fill: "black",
                selectable: false,
                evented: false,
                width: 100,
                textAlign: "left",
                originX: "left",
                originY: "top",
              }
            );

            this.canvas.add(label);
            this.labelMap.set(rect, label);
            this.updateLabelPosition(rect);
            this.canvas.bringToFront(rect);
            this.canvas.requestRenderAll();
          }, 1000); 
        }

        this.isDrawing = false;
        this.drawingRect = null;
        this.hasMoved = false;
      });

      this.canvas.on("mouse:wheel", (opt) => {
        const delta = opt.e.deltaY;
        let zoom = this.canvas.getZoom();
        zoom *= 0.999 ** delta;
        zoom = Math.min(Math.max(zoom, 0.5), 3);
        const point = this.canvas.getPointer(opt.e);
        this.canvas.zoomToPoint(point, zoom);
        opt.e.preventDefault();
        opt.e.stopPropagation();
      });

      this.canvas.on("object:moving", (opt) => {
        if (opt.target && opt.target.type === "rect") {
          this.updateLabelPosition(opt.target);
        }
      });

      this.canvas.on("object:scaling", (opt) => {
        if (opt.target && opt.target.type === "rect") {
          this.updateLabelPosition(opt.target);
        }
      });

      this.canvas.on("object:rotating", (opt) => {
        if (opt.target && opt.target.type === "rect") {
          this.updateLabelPosition(opt.target);
        }
      });

      this.canvas.on("selection:created", this.handleSelection);
      this.canvas.on("selection:updated", this.handleSelection);
      this.canvas.on("selection:cleared", this.clearHighlight);
    },

    updateLabelPosition(rect) {
      const label = this.labelMap.get(rect);
      if (!label) return;

      const offset = 5;
      const labelLeft = rect.left;
      const labelTop = rect.top - label.height - offset;

      label.set({
        left: labelLeft,
        top: labelTop,
        angle: 0,
        scaleX: 1,
        scaleY: 1,
      });
    },

    loadBackgroundImage() {
      const imageUrl = "https://fabricjs.cc/assets/github.svg";
      fabric.Image.fromURL(imageUrl, (img) => {
        img.set({
          originX: "left",
          originY: "top",
          selectable: false,
          evented: false,
        });

        const scaleX = this.canvasWidth / img.width;
        const scaleY = this.canvasHeight / img.height;
        img.scaleX = scaleX;
        img.scaleY = scaleY;

        this.canvas.setBackgroundImage(
          img,
          this.canvas.requestRenderAll.bind(this.canvas),
          {
            originX: "left",
            originY: "top",
          }
        );

        this.imageBounds = {
          left: 0,
          top: 0,
          right: img.width * scaleX,
          bottom: img.height * scaleY,
        };
      });
    },

    isPointInsideImage(point) {
      if (!this.imageBounds) return false;
      const { left, top, right, bottom } = this.imageBounds;
      return point.x >= left && point.x <= right && point.y >= top && point.y <= bottom;
    },

    isRectInsideImage(left, top, right, bottom) {
      if (!this.imageBounds) return false;
      const bounds = this.imageBounds;
      return (
        left >= bounds.left &&
        top >= bounds.top &&
        right <= bounds.right &&
        bottom <= bounds.bottom
      );
    },

    handleSelection(e) {
      this.clearHighlight();
      const activeObj = e.selected?.[0];
      if (activeObj && activeObj.type === "rect") {
        activeObj.set({
          stroke: "darkred",
          fill: "rgba(255, 0, 0, 0.3)",
        });
        this.canvas.requestRenderAll();
      }
    },

    clearHighlight() {
      this.canvas.getObjects("rect").forEach((obj) => {
        obj.set({
          stroke: "green",
          fill: "rgba(0, 255, 0, 0.3)",
        });
      });
      this.canvas.requestRenderAll();
    },

    bindKeyboardEvents() {
      window.addEventListener("keydown", this.handleKeyDown);
      window.addEventListener("keyup", this.handleKeyUp);
    },

    handleKeyDown(e) {
      if (e.key === "Control") {
        this.isCtrlPressed = true;
      }
    },

    handleKeyUp(e) {
      if (e.key === "Control") {
        this.isCtrlPressed = false;
        this.canvas.isDragging = false;
      }
    },
  },

  beforeDestroy() {
    window.removeEventListener("keydown", this.handleKeyDown);
    window.removeEventListener("keyup", this.handleKeyUp);
    this.canvas.dispose();
  },
};
</script>

<style scoped>
canvas {
  display: block;
  margin: 0 auto;
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猛男敲代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值