通过canvas框架fabric JS实现图片标注功能

4,300 阅读1分钟

✨ 效果图

chat8.gif

☀️ 需求

  • 基于一张图片,在图片上绘制出相应的标注框,并将标注框对应的坐标以及宽高传送给后端

使用fabric

  • 由于原生canvas Api繁杂。打算用基于canvas的fabric框架,fabric提供了大量简洁的api。

📝引入

1.引入fabric

npm i fabric

  1. 安装后在对应页面添加如下代码将其引入
import { fabric } from "fabric";

🌰 举个例子(vue3)

  • tempalte
<template>
  <div class="home">
    <canvas id="canvas"></canvas>
    <div class="operate">
      <div class="btn" @click="getCanvasObj">获取标注对象</div>
    </div>
  </div>
</template
  • JS
<script setup>
import { onMounted } from "vue";
import { fabric } from "fabric";

onMounted(() => {
  //监听键盘输入
  document.onkeydown = function () {
    let key = window.event.keyCode;
    
    if (key === 49) { //点击键盘 数字"1"后可以开始绘制标注框
      createRect();
      canvas.skipTargetFind = true;
    } else if (key === 46) { //选中标注框对象 再点击键盘"delete"键可以删除标注框
      if (
        canvas.getActiveObject() &&
        canvas.getActiveObject()?._objects?.length > 1
      ) {
        canvas.getActiveObject()._objects.forEach((element) => {
          canvas.remove(element);
        });
        canvas.discardActiveObject();
      } else if (canvas.getActiveObject()) {
        canvas.remove(canvas.getActiveObject());
      }
    }
  };

  init();
});

/**
 * @name: 初始化
 * */
let canvas;
function init() {
  canvas = new fabric.Canvas("canvas", {
    backgroundColor: "rgb(100,100,200)", // 画布背景色
    selectionColor: "rgba(255,255,255,0.3)", // 画布中鼠标框选背景色
    selectionLineWidth: 0, // 画布中鼠标框选边框1
    // selection: false, // 在画布中鼠标是否可以框选 默认为true
  });
  // createRect();
  insertImg();
}

/**
 * @name: 绘制矩形
 */
let dtop = 0;
let dleft = 0;
let dw = 0;
let dh = 0;
let rect;

let imgUrl = require("@/assets/logo.webp"); //需要绘制的图片
function createRect(row) {
  canvas.on("mouse:down", (options) => {
    dleft = options.e.offsetX;
    dtop = options.e.offsetY;
  });
  canvas.on("mouse:up", (options) => {
    let offsetX =
      options.e.offsetX > canvas.width ? canvas.width : options.e.offsetX;
    let offsetY =
      options.e.offsetY > canvas.height ? canvas.height : options.e.offsetY;

    dw = Math.abs(offsetX - dleft);
    dh = Math.abs(offsetY - dtop);
    // 拦截点击
    if (dw === 0 || dh === 0) {
      return;
    }

    rect = new fabric.Rect({
      top: dtop > offsetY ? offsetY : dtop,
      left: dleft > offsetX ? offsetX : dleft,
      width: dw,
      height: dh,
      fill: "rgba(101,169,230,0.2)",
      stroke: "rgb(101,169,230)", // 边框原色
      strokeWidth: 2, // 边框大小1
      // angle: 15,
      // selectable: false, // 是否允许当前对象被选中
      lockRotation: true, // 不允许旋转
    });
    rect.set("strokeUniform", true); // 该属性在启用时可以防止笔划宽度受对象的比例值影响
    canvas.add(rect);
    stopDraw();

    canvas.skipTargetFind = false; //设置对象能否选中
  });
    
  canvas.on("mouse:move", (options) => {
    if (options.target) {
      objectMoving(options);
    }
  });
}

const insertImg = () => {
  // 插入背景
  let image = new Image();
  image.src = imgUrl;
  image.onload = () => {
    // 绘制图片
    // 设置canvas宽高
    canvas.setWidth(image.width);
    canvas.setHeight(image.height);

    fabric.Image.fromURL(imgUrl, (img) => {
      img.set({
        scaleX: 1,
        scaleY: 1,
        left: 0,
        top: 0,
      });
      canvas.setBackgroundImage(img, canvas.renderAll.bind(canvas));
    });
  };
};

// 释放canvas监听
const stopDraw = () => {
  canvas.off("mouse:down");
  canvas.off("mouse:up");
};

// 限制对象的 不超出画布
function objectMoving(e) {
  var obj = e.target;
  if (!obj) return;
  if (
    obj.currentHeight > obj.canvas.height ||
    obj.currentWidth > obj.canvas.width
  ) {
    return;
  }
  obj.setCoords();
  // top-left corner
  if (obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0) {
    obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top);
    obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left);
  }
  // bot-right corner
  if (
    obj.getBoundingRect().top + obj.getBoundingRect().height >
      obj.canvas.height ||
    obj.getBoundingRect().left + obj.getBoundingRect().width > obj.canvas.width
  ) {
    obj.top = Math.min(
      obj.top,
      obj.canvas.height -
        obj.getBoundingRect().height +
        obj.top -
        obj.getBoundingRect().top
    );
    obj.left = Math.min(
      obj.left,
      obj.canvas.width -
        obj.getBoundingRect().width +
        obj.left -
        obj.getBoundingRect().left
    );
  }
}

// 获取所有标注对象
function getCanvasObj() {
  console.log("canvas._objects", canvas._objects);
}
</script>
  • style
<style scoped>
.home {
  width: 100%;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
#canvas {
}
.operate {
  margin-top: 30px;
}
.btn {
  display: inline-flex;
  justify-content: center;
  align-items: center;
  line-height: 1;
  height: 32px;
  white-space: nowrap;
  cursor: pointer;
  padding: 8px 15px;
  border: 1px solid #ddd;
}
</style>