需求收集
做這個(gè)組件的初衷渤昌,是基于AI組的標(biāo)注識(shí)別恒傻,傳送一張圖片以及圖片上的一些坐標(biāo),返回對應(yīng)的識(shí)別結(jié)果畏纲,前端要做的就是基于一張圖片扇住,在圖片上繪制出相應(yīng)的標(biāo)注框春缕,并將標(biāo)注框?qū)?yīng)的坐標(biāo)以及寬高傳送給后端進(jìn)行識(shí)別,這是最基礎(chǔ)的需求台囱。在圖片上進(jìn)行繪制淡溯,首先想到的是用canvas
,cancas
強(qiáng)大的功能能讓我們在圖片上為所欲為簿训,原生的canvas
api眾多且繁雜咱娶,上手不易,fabric
是一個(gè)基于canvas的強(qiáng)大的框架强品,提供一種類似面向?qū)ο蟮姆椒▉砭帉慶anva,在原生canvas之上提供了交互式對象模型膘侮,通過簡潔的api就可以在畫布上進(jìn)行豐富的操作。因此選擇fabric
來作為基礎(chǔ)框架的榛。
fabric.js介紹
fabric
是基于canvas進(jìn)行的api封裝琼了,可以實(shí)現(xiàn)繪制矩形、圓夫晌、橢圓雕薪、文本等一些基礎(chǔ)圖形,同時(shí)支持畫筆自定義圖形晓淀,fabric
的優(yōu)點(diǎn)在于它對生成的canvas畫布進(jìn)行了良好的封裝所袁,包括對畫布以及畫布上的對象進(jìn)行調(diào)整,監(jiān)聽畫布和對象的各種事件凶掰,使得畫布交互邏輯變得簡單易上手燥爷。fabric
的官網(wǎng)詳細(xì)地列出了fabric的各種參數(shù)以及api,由于Fabric.js是國外的框架,文檔為全英文懦窘,且相關(guān)示例少前翎,所以建議配合源碼使用
功能
構(gòu)建畫布
- 根據(jù)圖片生成基礎(chǔ)畫布
首先組件從外部接收圖片鏈接
props:{
imgData: String // 圖片鏈接
}
watch
監(jiān)聽imageData
變化,并生成畫布
watch:{
imageData(val){
if(val){
this.fabricCanvas() // 生成畫布
}
}
}
fabricCanvas
事件主要是初始化fabric畅涂,并將圖片設(shè)置成畫布的背景圖片港华,以便后續(xù)在畫布上添加標(biāo)注框
<template>
<div id="canvax-box">
<canvas id="label-canvas" :width="width" :height="height">
</div>
</template>
<script>
export default{
methods:{
fabricCanvas(){
if(this.fabricObj){ // 如果畫布已經(jīng)存在,清空畫布重新繪制
this.fabricObj.clear()
} else {
this.fabricObj = new fabric.Canvas('lavel-canvas',{
// 此處設(shè)置畫布的初始屬性
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設(shè)置成圖片的原始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>
- 監(jiān)聽畫布事件
fabric
提供了一系列的事件幫助我們來很好的對畫布進(jìn)行各種操作
此次主要用到以下幾個(gè)事件
watch:{
imageData(val){
if(val){
this.fabricCanvas() // 生成畫布
this.fabricObjEvent() // 監(jiān)聽畫布事件
}
}
}
畫布操作
標(biāo)注畫框
標(biāo)注畫框主要用到的是上述中的mouse:down
:畫筆落下;mouse:move
畫框;mouse:up
畫筆抬起事件
調(diào)整畫框
在調(diào)整畫框之前午衰,首先要將畫布設(shè)置為可選擇
如果想要修改畫框的默認(rèn)選中樣式立宜,可修改畫框的對應(yīng)參數(shù)即可
調(diào)整畫框主要用到上述的
object:moving
:對象移動(dòng);object:modified
:對象調(diào)整苇经;
handleObjectMoving(){
// 阻止對象移動(dòng)到畫布外面
let padding = 0; // 內(nèi)容距離畫布的空白寬度赘理,主動(dòng)設(shè)置
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)
}
選中畫框
畫框被選中時(shí),可拋出選中事件
// rect setRect()方法中生成的畫框
rect.on('selected',(e)=>{
this.$emit('objectSelected', e.target)
})
刪除畫框
調(diào)用fabric
的remove事件即可
this.fabricObj.remove(item)
清空所有畫框
clearAllMark(){
const objects = this.fabricObj.getObjects()
for(let i in objects){
this.fabricObj.remove(i)
}
this.$emit('clearAllMark')
}
根據(jù)坐標(biāo)生成畫框
- 生成單個(gè)畫框
- 批量生成
預(yù)覽
使用css的transform
來對畫布進(jìn)行放大縮小和拖拽操作
放大縮小
- 放大
- 縮小
- 還原