摘要:前言熟悉的朋友想必都使用或者聽說過,算是一個元老級的庫了,從第一個版本發(fā)布到現(xiàn)在,已經有年時間了。中緩存是默認開啟的,同時也可以設置為禁用。處理屏屏幕模糊的問題,直接給出處理方法,就不展開說了。
前言
熟悉 canvas 的朋友想必都使用或者聽說過 Fabric.js,F(xiàn)abric 算是一個元老級的 canvas 庫了,從第一個版本發(fā)布到現(xiàn)在,已經有 8 年時間了。我近一年時間也在項目中使用,作為用戶簡單說說感受:
方便,只有想不到,沒有做不到
源碼寫的真好,代碼規(guī)范,注釋清晰
社區(qū)真匱乏,國內資源尤其少
看文檔不如看源碼
優(yōu)缺點都很鮮明,但總的來說,如果你要做一個在線編輯類的項目,比如在線 PPT,在線制圖等應用,fabric 絕對是個很好的選擇。
那么這一系列文章要寫什么?這里不會主要介紹如何使用 fabric,主要寫的內容是把在閱讀源碼過程中,把涉及到原理相關的知識總結出來,比如相關圖形學知識、canvas 相關、fabric 中的設計思想等的相關知識。所以,如果你現(xiàn)在還對 fabric 不是很了解,建議先去官網找?guī)讉€ demo 試一下。
下面我們進入這次的正題,這篇文章主要介紹 fabric.canvas 涉及到的部分內容。
從創(chuàng)建畫布開始fabric 創(chuàng)建畫布很簡單:
const canvas = new fabric.Canvas("domId", options);
在這樣一行代碼背后,fabric 主要做了下面這幾件事情:
創(chuàng)建緩存 canvas
構建兩層 canvas 元素:lower-canvas 和 upper-canvas
綁定事件
處理 retina 屏
...
下面我把相關內容一一闡述。
canvas 緩存介紹 canvas 緩存,fabric 中的緩存也是類似的道理,簡單來說,就是使用一個離屏 canvas 來做預渲染,在真實畫布上用 drawImage 代替直接繪制圖形。
我們先來看個 例子,大家可以把 FPS meter 打開,切換按鈕可以看到,不使用緩存和使用緩存 FPS 值差距還是挺大的,我電腦在使用緩存的時候基本在 60fps,不使用會降到 15fps 左右。大家可以打開控制臺或者在 這里 查看代碼。
下面列出主要的代碼片段:
class Ball { constructor(x, y, vx, vy, useCache = true) { // ... if (useCache) { this.useCache = useCache; this.cacheCanvas = document.createElement("canvas"); // 離屏 canvas 寬高取要渲染圖形的寬高,不可以取真實 canvas 的寬高,否則會渲染大量無用區(qū)域 this.cacheCanvas.width = 2 * (this.r + BORDER_WIDTH); this.cacheCanvas.height = 2 * (this.r + BORDER_WIDTH); this.cacheCtx = this.cacheCanvas.getContext("2d"); this.cache(); } } paint() { // 使用緩存直接使用創(chuàng)建的離屏canvas,否則直接繪制圖形 if (!this.useCache) { ctx.save(); ctx.lineWidth = BORDER_WIDTH; ctx.beginPath(); ctx.strokeStyle = this.color; ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI); ctx.stroke(); ctx.restore(); } else { ctx.drawImage( this.cacheCanvas, this.x - this.r, this.y - this.r, this.cacheCanvas.width, this.cacheCanvas.height ); } } move() { // ... } cache() { // 繪制圖形 this.cacheCtx.save(); this.cacheCtx.lineWidth = BORDER_WIDTH; this.cacheCtx.beginPath(); this.cacheCtx.strokeStyle = this.color; this.cacheCtx.arc( this.r + BORDER_WIDTH, this.r + BORDER_WIDTH, this.r, 0, 2 * Math.PI ); this.cacheCtx.stroke(); this.cacheCtx.restore(); } }
解釋一下二者區(qū)別:
使用緩存:在實例化每個圖形的時候(渲染之前),先將圖形渲染到一個離屏的 canvas 上,在渲染的時候,直接用 drawImage 將離屏的 canvas 渲染。
不使用緩存: 在渲染的時候直接繪制圖形
使用緩存的時候,有一點需要注意的是要控制好離屏 canvas 的大小,不可以直接取和渲染 canvas 的實際寬高,否則會渲染很多無用的空間,比如上面例子中每個離屏 canvas 的寬高只需要和對應圖形的寬高一致。
this.cacheCanvas.width = 2 * (this.r + BORDER_WIDTH); this.cacheCanvas.height = 2 * (this.r + BORDER_WIDTH);
上述代碼中主要節(jié)省時間的地方在 paint 函數中使用 drawImage會比直接繪制圖形節(jié)省時間,那么是否所有場景都是這樣呢?我們再來看下面這個 例子.
這個例子和上面的只有繪制圖形的代碼不同:
// 從復雜圖形變成了簡單圖形 cache() { this.cacheCtx.save(); this.cacheCtx.lineWidth = BORDER_WIDTH; this.cacheCtx.beginPath(); this.cacheCtx.strokeStyle = this.color; this.cacheCtx.arc( this.r + BORDER_WIDTH, this.r + BORDER_WIDTH, this.r, 0, 2 * Math.PI ); this.cacheCtx.stroke(); this.cacheCtx.restore(); }
只是cache方法中把復雜圖形變成了簡單的圖形。但實際效果相差甚遠,使用緩存和不使用性能差距并不大,甚至不使用時 fps 值還更高一些。
所以看來圖形的復雜度,直接會影響 canvas 緩存的效果,我們在開發(fā)過程中,也不能盲目引入緩存,要權衡利弊。fabric 中緩存是默認開啟的,同時也可以設置 objectCaching 為 false 禁用。
lower-canvas 和 upper-canvas如果大家細心的話應該會發(fā)現(xiàn),當我們執(zhí)行new fabric.Canvas("domeId")的時候,在頁面上 dom 元素就改變了,fabric 復制了一層 canvas 蓋在了我們定義的 canvas 上面:
fabric 這樣設計將渲染層和交互層做了分離,lower-canvas 只負責渲染元素;所有的交互,比如框選,事件處理都在 upper-canvas 上。
順便提一下,fabric 提供了渲染靜態(tài)畫布的方法,如果你的畫布不需要任何交互,只用來展示,那么可以用new fabric.StaticCanvas("domId", options)來初始化,這時候 dom 結構中就只有一個 canvas,沒有 upper-canvas 了。
說到這里,很多同學可能會想到,事件是怎樣綁定的呢?其實兩個 canvas 大小等屬性都是一致的,所以坐標也是可以對應上的,比如在 upper-canvas 上某個位置點擊了一下,那么就可以去 lower-canvas 上就可以用這個坐標去找是否點擊到了一個元素,那么問題來了,如何判斷一個點在一個圖形中呢?
如何判斷點在圖形中這個問題網上有個比較普遍的方案,就是通過畫一條射線,通過交點奇偶性來判斷。如下圖:
設目標點 P,使 P 點向任意一個方向畫一條射線,保證不與圖形的頂點相交;
記錄射線與圖形的交點數量 n;
n 為奇數時,P 就在圖形內,反之則在圖形外。
而 fabric 中并沒有用這種方法,原因很簡單,這個算法是有前提的:發(fā)出的射線不能與圖形任何頂點相交。 這個前提對于我們主觀來判斷是很簡單的,但程序中處理可能就需要大量的代碼去判斷是否與交點相交,如果相交再重新生成一條射線。
fabric 中使用的算法對上述算法進行了改進,我們結合下圖來解釋:
其中 e1 ~ e5 分別為多邊形的邊,P 為目標點,黑色實心點為多邊形的頂點,r 為 P 延 X 軸發(fā)出的射線(不同于上面的方法,這里我們約定 r 射線只能延 X 軸發(fā)出)。
設目標點 P,使 P 延 X 軸方向畫一條射線( y=Py ),設 intersectionCount = 0
遍歷多邊形的所有邊,設邊的頂點為 p1, p2
如果 p1y < Py,而且 p2y < Py,跳過(也就是這條邊在 P 點下方)
如果 p1y >= Py,而且 p2y >= Py,跳過(也就是這條邊在 P 點上方)
否則,設射線與這條邊的交點為 S,如果 Sx >= Px,intersectionCount加 1
最終如果intersectionCount為奇數,則在圖形內,反之則在圖形外。
判斷的部分用代碼實現(xiàn)類似:
// point 目標點,lines多邊形的所有邊 function checkPoint(point, lines) { let intersectionCount = 0; let { x, y } = point; for (let i = 0; i < lines.length; i++) { let line = lines[i]; // 兩個頂點 let { p1, p2 } = line; if ((p1.y < y && p2.y < y) || (p1.y >= y && p2.y >= y)) { continue; } else { const sx = ((y - p1.y) / (p2.y - p1.y)) * (p2.x - p1.x) + p1.x; if (sx >= x) { intersectionCount++; } } } return intersectionCount % 2 === 0; }
這里是個簡單的例子。同時 這里 可以獲取完整代碼。
處理 Retina 屏Retina 屏幕模糊的問題,直接給出處理方法,就不展開說了。
canvas.width, canvas.height 放大至 dpi 倍
canvas.style.width, canvas.style.height 設為原始 canvas 寬高
ctx 縮放 dpi 倍
代碼:
function initRetina(canvas, ctx) { const dpi = window.devicePixelRatio; canvas.style.width = canvas.width + "px"; canvas.style.height = canvas.height + "px"; canvas.setAttribute("width", canvas.width * dpi); canvas.setAttribute("height", canvas.height * dpi); ctx.scale(dpi, dpi); }
查看例子,完整代碼
小結本篇文章主要針對fabric.canvas模塊,介紹了相關 canvas 緩存,fabric 中判斷點在圖形中的算法以及如何處理 retina 屏幕的知識,作為系列的第一篇文章,可能會有很多問題,如有錯誤及意見,歡迎批評指正。
參考文獻:
http://idav.ucdavis.edu/~okre...
http://www.geog.ubc.ca/course...
https://www.cnblogs.com/axes/...
http://fabricjs.com/docs/
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/104044.html
摘要:用于設置縮放比例,可以是任意數值的比例。禁止,不禁止是否就職瀏覽器識別頁面中的郵件地址。具體可參考如下代碼以前,我不知道或中添加有什么用,但上面的例子是它的一個用途。其他的使用可參考或模擬原生效果。更多的前端相關資源,可關注我的 meta meta中有這樣幾個常用屬性:http-equiv,name,content,包括html5新增的charset。 注意:content屬性用來存儲...
摘要:今天要講的,是我從的源碼實現(xiàn)文件中學到的幾個很基礎,卻又容易被忽略的知識點。在函數式編程中,函數是一等公民,它可以只是根據參數,做簡單的組合操作,再作為別的函數的返回值。所以,閱讀源碼,是一種很棒的重溫基礎知識的方式。 showImg(https://segmentfault.com/img/bVbpTSY?w=750&h=422); 前言 上一篇文章 「前端面試題系列8」數組去重(1...
閱讀 3537·2021-09-10 10:51
閱讀 2507·2021-09-07 10:26
閱讀 2482·2021-09-03 10:41
閱讀 810·2019-08-30 15:56
閱讀 2896·2019-08-30 14:16
閱讀 3488·2019-08-30 13:53
閱讀 2103·2019-08-26 13:48
閱讀 1913·2019-08-26 13:37