需求收集
做这个组件的初衷,是基于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 = () => {
// 将canvas的width和height设置成图片的原始width,height
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
来对画布进行放大缩小和拖拽操作
放大缩小
- 放大
- 缩小
- 还原