上篇文章大致介紹了使用Vue + fabric.js構(gòu)建標(biāo)注工具的流程退疫,本篇?jiǎng)t將其中的一些細(xì)節(jié)以及fabric
的踩坑進(jìn)行補(bǔ)充
1.鼠標(biāo)從右向左畫框
承接上篇的描述,使用fabric
在canvas上畫標(biāo)注框的流程主要為:
- 監(jiān)聽畫布的鼠標(biāo)按下
mouse:down
事件批什,并保存鼠標(biāo)按下時(shí)的坐標(biāo)混驰,作為標(biāo)注框的起點(diǎn)(mouseFrom
)昧捷; - 監(jiān)聽畫布的鼠標(biāo)移動(dòng)
mouse:move
事件,在鼠標(biāo)移動(dòng)過程中堤框,在canvas上繪制以第一步中的起點(diǎn)為左上角域滥,鼠標(biāo)移動(dòng)時(shí)的坐標(biāo)為右下角(mouseTo
)的矩形(rect); - 監(jiān)聽畫布的鼠標(biāo)抬起
mouse:up
事件蜈抓,鼠標(biāo)抬起時(shí)启绰,標(biāo)注框繪制完畢;
由此得知沟使,在第二步中的標(biāo)注框的生成代碼為
rect = new fabric.Rect({
left: mouseFrom.x,
top: mouseFrom.y,
width: mouseTo.x - mouseFrom.x,
height: mouseTo.y - mouseFrom.y
})
然而這樣設(shè)置存在一個(gè)隱患bug,當(dāng)鼠標(biāo)從左向右畫框時(shí)委可,標(biāo)注框正常,但當(dāng)鼠標(biāo)從右向左畫框時(shí),發(fā)現(xiàn)標(biāo)注框并不能如我們所期望的隨著鼠標(biāo)移動(dòng)着倾,而是一直向右畫框
針對上面場景拾酝,一個(gè)解決方案為
在繪制框時(shí),先判斷mouseFrom.x
和mouseTo.x
,mouseFrom.y
和mouseTo.y
的大小卡者,以較小的那個(gè)值為標(biāo)注框的左上角的坐標(biāo)(left
和top
)蒿囤,以mouseTo.x-mouseFrom.x
的絕對值為標(biāo)注框的寬(width
),以mouseTo.y-mouseFrom.y
的絕對值為標(biāo)注框的高(height
)
let x = Math.min(mouseFrom.x, mouseTo.x)
let y = Math.min(mouseFrom.y, mouseTo.y)
let width = Math.abs(mouseTo.x-mouseFrom.x)
let height = Math.abs(mouseTo.y-mouseFrom.y)
rect = new fabric.Rect({
left: x,
top: y,
width: width,
height: height
})
以這樣的方法使得標(biāo)注框的左上定點(diǎn)是相對小的那個(gè)值崇决,雖然rect仍舊是從左畫到右材诽,但隨著鼠標(biāo)的移動(dòng),視覺上rect是隨著鼠標(biāo)從右向左畫
2.標(biāo)注框溢出畫布
- 繪制過程中標(biāo)注框溢出畫布
緊接著上步所說的跟隨著鼠標(biāo)移動(dòng)繪制標(biāo)注框恒傻,當(dāng)鼠標(biāo)在畫布內(nèi)的時(shí)候脸侥,標(biāo)注框正常繪制,但是盈厘,當(dāng)鼠標(biāo)移出畫布時(shí)睁枕,mouseFrom
和mouseTo
的值仍在變化,但是溢出畫布的標(biāo)注框卻不能正常顯示沸手,因此在繪制時(shí)外遇,需要限制mouseFrom
和mouseTo
的值,使得標(biāo)注框的起點(diǎn)和終點(diǎn)均保持在畫布內(nèi)部。
limitPoint(x,y){
if(x < 0) x = 0
if(y < 0) y = 0
// fabricObj為使用fabric創(chuàng)建的canvas對象罐氨,this.fabricObj.getWidth()獲取畫布的寬
if(x > this.fabricObj.getWidth()) x = this.fabricObj.getWidth()
// this.fabricObj.getHeight()獲取畫布的高
if(y > this.fabricObj.getHeight()) y = this.fabricObj.getHeight()
}
[圖片上傳失敗...(image-29416e-1630578659128)]
- 移動(dòng)標(biāo)注框過程中溢出畫布
canvas.on('object:moving', (e) => {
// 阻止對象移動(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
);
}
})
3.屏幕分辨率引起的選中狀態(tài)下的框移位
在開發(fā)過程中滩援,我遇到過這樣一個(gè)bug,起初在外接顯示器上栅隐,選中標(biāo)注框正常,但無意間拖動(dòng)到自己電腦屏幕上時(shí)玩徊,詭異的一幕發(fā)生了租悄,選中的框跟原本的標(biāo)注框不對應(yīng),再拖回到外接顯示器上囊扳,又顯示正常了
選中狀態(tài)下選中選中框的八個(gè)控制點(diǎn)沒有很好的附著在選中框上
看到這個(gè)問題授艰,著實(shí)讓人頭疼曙博,明明什么都沒動(dòng),為啥會(huì)出現(xiàn)這樣的bug?逐一對比在外接顯示器和自己電腦屏幕上console出來的被選中的標(biāo)注框的各個(gè)字段潭辈,發(fā)現(xiàn)zoomX
和zoomY
在外接顯示器上為1,在自己電腦屏幕上為1.25澈吨,不由懷疑是zoomX
和zoomY
這兩個(gè)字段導(dǎo)致的標(biāo)注框偏移把敢,然后去研究源碼,找到在創(chuàng)建標(biāo)注框rect時(shí)zoomX
和zoomY
的賦值邏輯
fabric
是通過drawControls()
函數(shù)繪制選中狀態(tài)下的控制點(diǎn)的谅辣,其中紅線框的部分發(fā)現(xiàn)設(shè)置了transform
,緊接著懷疑是canvas的getRetinalScaling()
影響到了zoomX
和zoomY
找到
getRetinalScaling()
的取值函數(shù)修赞,發(fā)現(xiàn)是根據(jù)_isRetinaScaling()
函數(shù)來決定取fabric.devicePixelRatio
還是默認(rèn)值1,不理解fabric.devicePixelRatio
是什么桑阶,就接著去找fabric.devicePixelRatio
的定義window.devicePixelRatio
到這柏副,恍然大悟勾邦,檢查自己電腦的分配率設(shè)置,果然是125%割择,與上面所述打印出來的rect的
zoomX
和zoomY
對應(yīng)眷篇,試著將分辨率改成100%,發(fā)現(xiàn)zoomX
和zoomY
值變?yōu)?锨推,選中狀態(tài)下的控制點(diǎn)也顯示正常了
理清bug出現(xiàn)的原因后铅歼,自然而然就想到,解決此bug的關(guān)鍵點(diǎn)在于不能讓window.devicePixelRatio
成為控制點(diǎn)的縮放因子换可,問題又回到了getRetinalScaling()
椎椰,如果_isRetinaScaling()
為false,那不管屏幕分辨率是多少,getRetinalScaling()
值都取1沾鳄,控制點(diǎn)不就顯示正常了慨飘?
然后接著去找
_isRetinaScaling()
的取值發(fā)現(xiàn)fabric的canvas有一個(gè)
enableRetinaScaling
參數(shù),默認(rèn)值為true,官網(wǎng)給出的參數(shù)含義為
單看文檔译荞,確實(shí)不知所云瓤的,但通過源碼,很好的就理解了參數(shù)的含義吞歼,感嘆一聲圈膏,文檔還是要配合源碼觀看效果更佳!
4.選中狀態(tài)下調(diào)整框的等比例縮放問題
開發(fā)完之后篙骡,產(chǎn)品提出這樣一個(gè)bug,調(diào)整標(biāo)注框拖動(dòng)上下左右四個(gè)角只能等比例縮放稽坤,產(chǎn)品期望能隨著鼠標(biāo)自由地縮放,瀏覽一遍文檔糯俗,沒有找到對應(yīng)的設(shè)置尿褪,那就只能再去源碼里面找了,尋找的過程在這里就不啰嗦了得湘,總而言之杖玲,通過自下而上地翻閱源碼,發(fā)現(xiàn)fabric的canvas有一個(gè)uniformScaling
屬性控制著標(biāo)注框的等比例縮放淘正,且默認(rèn)值為true,將其設(shè)置成false后摆马,bug就迎刃而解了
[圖片上傳失敗...(image-8f96a8-1630578659128)]
5.圖片分辨率不同,標(biāo)注框的寬度設(shè)置
由于不同的圖片分辨率差異較大鸿吆,如果以同一種寬度來設(shè)置標(biāo)注框今膊,呈現(xiàn)效果相差較大,因此采取根據(jù)圖片分辨率來動(dòng)態(tài)設(shè)置標(biāo)注框?qū)挾龋╯cale為上篇文章中創(chuàng)建畫布階段伞剑,圖片寬高與畫布容器寬高的比值)
<div id="canvax-box">
<canvas id="label-canvas" :width="width" :height="height">
</div>
</template>
<script>
export default{
methods:{
fabricCanvas(){
...
// 將圖片放置在外部容器中
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
...