離屏Canvas — 使用Web Worker提高你的Canvas執行速度
原文連結: developers.google.com
現在因為有了離屏Canvas,你可以不用在你的主執行緒中繪製影像了!
Canvas 是一個非常受歡迎的表現方式,同時也是WebGL的入口。它能繪製圖形,圖片,展示動畫,甚至是處理視訊內容。它經常被用來在富媒體web應用中建立炫酷的使用者介面或者是製作線上(web)遊戲。
它是非常靈活的,這意味著繪製在Canvas的內容可以被程式設計。舉個?,JavaScript就提供了Canvas的系列API。這些給了Canvas非常好的靈活度。
但同時,在一些現代化的web站點,指令碼解析執行是實現流暢使用者反饋的最大的問題之一。因為Canvas計算和渲染和使用者操作響應都發生在同一個執行緒中,在動畫中(有時候很耗時)的計算操作將會導致App卡頓,降低使用者體驗。
幸運的是, OffscreenCanvas 離屏Canvas可以非常棒的解決這個麻煩!
到目前為止,Canvas的繪製功能都與<canvas>標籤繫結在一起,這意味著Canvas API和DOM是耦合的。而OffscreenCanvas,正如它的名字一樣,通過將Canvas移出螢幕來解耦了DOM和Canvas API。
由於這種解耦,OffscreenCanvas的渲染與DOM完全分離了開來,並且比普通Canvas速度提升了一些,而這只是因為兩者(Canvas和DOM)之間沒有同步。但更重要的是,將兩者分離後,Canvas將可以在Web Worker中使用,即使在Web Worker中沒有DOM。這給Canvas提供了更多的可能性。
在Worker中使用OffscreenCanvas
Workers 是一個Web版的執行緒——它允許你在幕後執行你的程式碼。將你的一部分程式碼放到Worker中可以給你的主執行緒更多的空閒時間,這可以提高你的使用者體驗度。就像其沒有DOM一樣,直到現在,在Worker中都沒有Canvas API。
而OffscreenCanvas並不依賴DOM,所以在Worker中Canvas API可以被某種方法來代替。下面是我在Worker中用OffscreenCanvas來計算漸變顏色的?:
// file: worker.js function getGradientColor(percent) { const canvas = new OffscreenCanvas(100, 1); const ctx = canvas.getContext('2d'); const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0); gradient.addColorStop(0, 'red'); gradient.addColorStop(1, 'blue'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, ctx.canvas.width, 1); const imgd = ctx.getImageData(0, 0, ctx.canvas.width, 1); const colors = imgd.data.slice(percent * 4, percent * 4 + 4); return rgba(${colors[0]}, ${colors[1]}, ${colors[2]}, ${colors[]); } getGradientColor(40); // rgba(152, 0, 104, 255 )
不要阻塞主執行緒
當我們將大量的計算移到Worker中執行時,可以釋放主執行緒上的資源,這很有意思。我們可以使用transferControlToOffscreen 方法將常規的Canvas對映到OffscreenCanvas例項上。之後所有應用於OffscreenCanvas的操作將自動呈現在在源Canvas上。
const offscreen = document.querySelector('canvas').transferControlToOffscreen(); const worker = new Worker('myworkerurl.js'); worker.postMessage({ canvas: offscreen }, [offscreen]);
OffscreenCanvas 是 [可轉移的](https://developer.mozilla.org/en-US/docs/Web/API/Transferable))。除了將其指定為傳遞資訊中的欄位之一以外,還需要將其作為postMessage(傳遞資訊給Worker的方法)中的第二個引數傳遞出去,以便可以在Worker執行緒的context(上下文)中使用它。
在下面的?中,當顏色主題發生變化時會發生“複雜的計算”,這個計算即使在高效能的桌上型電腦上也要花費幾毫秒。而你可以選擇在主執行緒或Worker上執行這段動畫。在主執行緒下,當複雜計算開始執行時,你將無法與按鈕互動 - 執行緒被阻塞掉了。而在Worker下,UI的響應並沒有被影響。
它也是另一種解釋方式:任務繁忙的主執行緒也不會影響在Worker上執行的動畫。所以即使主執行緒非常繁忙,你也可以通過此功能來避免掉幀並保證流暢的動畫:
上例展示了在普通Canvas的下,當主執行緒被新增繁忙任務時動畫被阻塞了,而基於Worker的OffscreenCanvas播放卻很流利。
與流行庫一起使用
得益於OffscreenCanvas API一般情況下與常規Canvas元素的相API相容,你可以很輕鬆地漸進地使用它,也可以使用社群裡的一些優秀的圖形處理的庫/框架。
舉個?,你可以對其進行特徵檢測,如果可用的話,可通過在渲染的建構函式中指定canvas的配置項,然後實現與Three.js一起使用的功能:
const canvasEl = document.querySelector("canvas"); const canvas = ('OffscreenCanvas' in window) ? canvasEl.transferControlToOffscreen() : canvasEl; canvas.style = { width: 0, height: 0 } const renderer = new THREE.WebGLRenderer({ canvas: canvas });
上例的問題是Three.js需要Canvas具有style.width和style.height屬性。而OffscreenCanvas是與DOM完全分離的,沒有這些屬性。所以你需要自己提供這些屬性,或者通過將其從three.js邏輯中刪除或者自行編寫這些值與初始Canvas尺寸相關聯的邏輯。
下面是一個執行基本Three.js動畫的demo:
但是請記住,有一些與DOM相關的API在Worker中並不容易獲得,因此如果你想使用更高階的Three.js功能(比如紋理)的話,可能需要更多變通的方法。有關這方面已經開始嘗試的一些想法,請檢視 Google I/O 2017的視訊。
此視訊的示例中出現的commit()方法我們並不推薦。請改用worker.requestAnimationFrame。
結論
如果你對影像繪畫使用得非常多,OffscreenCanvas可以有效的提高你APP的效能。它使得Worker可以處理canvas的渲染繪製,讓你的APP更好地利用了多核系統。
OffscreenCanvas在Chrome 69中已經不需要開啟flag(實驗性功能)就可以使用了。它也正在被 Firefox 實現。由於其API與普通canvas元素非常相似,所以你可以輕鬆地對其進行特徵檢測並循序漸進地使用它,而不會破壞現有的APP或庫的執行邏輯。OffscreenCanvas在任何涉及到圖形計算以及動畫表現且與DOM關係並不密切(即依賴DOM API不多)的情況下,它都具有效能優勢。
其它資源
相關文章
- 使用Actor模型管理Web Worker多執行緒模型Web執行緒
- Web Worker執行緒的限制是什麼?Web執行緒
- 效能最佳化之使用vue-worker外掛(基於Web Worker)開啟多執行緒運算提高效率VueWeb執行緒
- 使用雙快取解決 Canvas clearRect 引起的閃屏問題快取Canvas
- Web Worker 使用教程Web
- canvas 2 image的使用小心得Canvas
- canvasCanvas
- 神奇的 CanvasCanvas
- web worker的介紹和使用Web
- web-worker 獨立執行緒,效能最佳化Web執行緒
- 【Web前端基礎知識】如何使用Canvas繪製圓形Web前端Canvas
- 擁抱並行流,提高程式執行速度並行
- Android自定義View之Canvas的使用AndroidViewCanvas
- 在Canvas中使用React HooksCanvasReactHook
- HTML5的SVG和Canvas的使用HTMLSVGCanvas
- WPF C# create canvas and draw ellipse in canvasC#Canvas
- canvas 如何自動去換行Canvas
- canvas rect()Canvas
- canvas strokeStyleCanvas
- canvas lineTo()Canvas
- canvas strokeRect()Canvas
- canvas createRadialGradient()Canvas
- canvas createLinearGradient()Canvas
- canvas arc()Canvas
- canvas stroke()Canvas
- canvas fill()Canvas
- canvas fillStyleCanvas
- canvas setTransform()CanvasORM
- canvas transform()CanvasORM
- 初探canvasCanvas
- canvas closePath()Canvas
- canvas moveTo()Canvas
- canvas beginPath()Canvas
- canvas getContext()CanvasContext
- canvas getImageData()Canvas
- canvas isPointInPath()Canvas
- canvas putImageData()Canvas
- canvas drawImage()Canvas