基于Vue + fabric.js的圖片標(biāo)注組件搭建

需求收集

做這個(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)行繪制淡溯,首先想到的是用canvascancas強(qiáng)大的功能能讓我們在圖片上為所欲為簿训,原生的canvasapi眾多且繁雜咱娶,上手不易,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)建畫布

  1. 根據(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>
  1. 監(jiān)聽畫布事件
    fabric提供了一系列的事件幫助我們來很好的對畫布進(jìn)行各種操作
image.png

此次主要用到以下幾個(gè)事件

watch:{
 imageData(val){
     if(val){
         this.fabricCanvas() // 生成畫布
         this.fabricObjEvent() // 監(jiān)聽畫布事件
      }
  }
}
image.png

畫布操作

標(biāo)注畫框

標(biāo)注畫框主要用到的是上述中的mouse:down:畫筆落下;mouse:move畫框;mouse:up畫筆抬起事件

image.png
image.png
image.png
image.png
image.png
image.png

調(diào)整畫框

在調(diào)整畫框之前午衰,首先要將畫布設(shè)置為可選擇


image.png

如果想要修改畫框的默認(rèn)選中樣式立宜,可修改畫框的對應(yīng)參數(shù)即可

image.png

調(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)生成畫框

  1. 生成單個(gè)畫框
image.png
  1. 批量生成
image.png

預(yù)覽

使用css的transform來對畫布進(jìn)行放大縮小和拖拽操作

image.png

放大縮小

  1. 放大
image.png
  1. 縮小
image.png
  1. 還原
image.png

拖拽

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扇单,一起剝皮案震驚了整個(gè)濱河市商模,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖施流,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件响疚,死亡現(xiàn)場離奇詭異,居然都是意外死亡瞪醋,警方通過查閱死者的電腦和手機(jī)忿晕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來银受,“玉大人践盼,你說我怎么就攤上這事”鑫。” “怎么了咕幻?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長顶霞。 經(jīng)常有香客問我肄程,道長,這世上最難降的妖魔是什么选浑? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任蓝厌,我火速辦了婚禮,結(jié)果婚禮上古徒,老公的妹妹穿的比我還像新娘拓提。我一直安慰自己,他們只是感情好描函,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布崎苗。 她就那樣靜靜地躺著狐粱,像睡著了一般琅翻。 火紅的嫁衣襯著肌膚如雪尿赚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音包警,去河邊找鬼。 笑死册着,一個(gè)胖子當(dāng)著我的面吹牛褐着,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播豆挽,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼育谬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了帮哈?” 一聲冷哼從身側(cè)響起膛檀,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后咖刃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泳炉,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年嚎杨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了花鹅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡枫浙,死狀恐怖刨肃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情箩帚,我是刑警寧澤之景,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站膏潮,受9級(jí)特大地震影響锻狗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜焕参,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一轻纪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧叠纷,春花似錦刻帚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至航厚,卻和暖如春顷歌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背幔睬。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來泰國打工眯漩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人麻顶。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓赦抖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親辅肾。 傳聞我的和親對象是個(gè)殘疾皇子队萤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容