✨ 效果图
☀️ 需求
- 基于一张图片,在图片上绘制出相应的标注框,并将标注框对应的坐标以及宽高传送给后端
使用fabric
- 由于原生canvas Api繁杂。打算用基于canvas的fabric框架,fabric提供了大量简洁的api。
📝引入
1.引入fabric
npm i fabric
- 安装后在对应页面添加如下代码将其引入
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>