需求收集
做这个组件的初衷,是基于AI组的标注识别,传送一张图片以及图片上的一些坐标,返回对应的识别结果,前端要做的就是基于一张图片,在图片上绘制出相应的标注框,并将标注框对应的坐标以及宽高传送给后端进行识别,这是最基础的需求。在图片上进行绘制,首先想到的是用canvas
,cancas
强大的功能能让我们在图片上为所欲为,原生的canvas
api众多且繁杂,上手不易,fabric
是一个基于canvas的强大的框架,提供一种类似面向对象的方法来编写canva,在原生canvas之上提供了交互式对象模型,通过简洁的api就可以在画布上进行丰富的操作。因此选择fabric
来作为基础框架。
fabric.js介绍
fabric
是基于canvas进行的api封装,可以实现绘制矩形、圆、椭圆、文本等一些基础图形,同时支持画笔自定义图形,fabric
的优点在于它对生成的canvas画布进行了良好的封装,包括对画布以及画布上的对象进行调整,监听画布和对象的各种事件,使得画布交互逻辑变得简单易上手。fabric
的官网详细地列出了fabric的各种参数以及api,由于Fabric.js是国外的框架,文档为全英文,且相关示例少,所以建议配合源码使用
功能
构建画布
- 根据图片生成基础画布
首先组件从外部接收图片链接
props:{
imgData: String
}
复制代码
watch
监听imageData
变化,并生成画布
watch:{
imageData(val){
if(val){
this.fabricCanvas()
}
}
}
复制代码
fabricCanvas
事件主要是初始化fabric,并将图片设置成画布的背景图片,以便后续在画布上添加标注框
<template>
<div id="canvax-box">
<canvas id="label-canvas" :width="width" :height="height">
</div>
</template>
复制代码
<script>
export default{
methods:{
fabricCanvas(){
if(this.fabricObj){
this.fabricObj.clear()
} else {
this.fabricObj = new fabric.Canvas('lavel-canvas',{
uniformScaling: false,
enableRetinaScaling: false,
selection: false
}
}
let Shape
const image = new Image()
image.src = this.imageData
image.setAttribute('crossOrigin','anonymous')
image.onload = () => {
this.width = image.width
this.height = image.height
this.fabricObj.setWidth(this.width)
this.fabricObj.setHeight(this.height)
let boxWidth = document.getElementById('canvas-box').offsetWidth
let boxHeight = document.getElementById('canvas-box').offsetHeight
let scaleX = boxWidth / image.width
let scaleY = boxHeight / image.height
this.scale = scaleX > scaleY ? scaleX : scaleY
document.querySelector('.canvas-container').style.width = this.width * this.scale + 'px'
document.querySelector('.canvas-container').style.height = this.height * this.scale + 'px'
document.querySelector('#label-canvas').style.width = this.width * this.scale + 'px'
document.querySelector('#label-canvas').style.height = this.height * this.scale + 'px'
document.querySelector('.upper-canvas').style.width = this.width * this.scale + 'px'
document.querySelector('.upper-canvas').style.height = this.height * this.scale + 'px'
Shape = new fabric.Image(image)
this.fabric.setBackgroundImage(Shape,
this.fabricObj.renderAll.bind(this.fabricObj),
{
opaity: 1,
angle: 0
}
)
this.$nextTick(()=>{
this.fabricObj.renderAll()
})
}
}
}
}
</script>
复制代码
- 监听画布事件
fabric
提供了一系列的事件帮助我们来很好的对画布进行各种操作
此次主要用到以下几个事件
watch:{
imageData(val){
if(val){
this.fabricCanvas()
this.fabricObjEvent()
}
}
}
复制代码

画布操作
标注画框
标注画框主要用到的是上述中的mouse:down
:画笔落下;mouse:move
画框;mouse:up
画笔抬起事件






调整画框
在调整画框之前,首先要将画布设置为可选择
如果想要修改画框的默认选中样式,可修改画框的对应参数即可
调整画框主要用到上述的object:moving
:对象移动;object:modified
:对象调整;
handleObjectMoving(){
let padding = 0;
var obj = e.target;
if (obj.currentHeight > obj.canvas.height - padding * 2 ||
obj.currentWidth > obj.canvas.width - padding * 2) {
return;
}
obj.setCoords();
if (obj.getBoundingRect().top < padding || obj.getBoundingRect().left < padding) {
obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top + padding);
obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left + padding);
}
if (obj.getBoundingRect().top + obj.getBoundingRect().height > obj.canvas.height - padding || obj.getBoundingRect().left + obj.getBoundingRect().width > obj.canvas.width - padding) {
obj.top = Math.min(
obj.top,
obj.canvas.height - obj.getBoundingRect().height + obj.top - obj.getBoundingRect().top - padding
);
obj.left = Math.min(
obj.left,
obj.canvas.width - obj.getBoundingRect().width + obj.left - obj.getBoundingRect().left - padding
);
}
}
复制代码
handleObjectModified(e){
this.$emit('objectModified',e.target)
}
复制代码
选中画框
画框被选中时,可抛出选中事件
// rect setRect()方法中生成的画框
rect.on('selected',(e)=>{
this.$emit('objectSelected', e.target)
})
复制代码
删除画框
调用fabric
的remove事件即可
this.fabricObj.remove(item)
复制代码
清空所有画框
clearAllMark(){
const objects = this.fabricObj.getObjects()
for(let i in objects){
this.fabricObj.remove(i)
}
this.$emit('clearAllMark')
}
复制代码
根据坐标生成画框
- 生成单个画框

- 批量生成

预览
使用css的transform
来对画布进行放大缩小和拖拽操作

放大缩小
- 放大
2. 缩小
3. 还原

拖拽
