離屏Canvas — 使用Web Worker提高你的Canvas執行速度

zcfy發表於2018-12-01

  原文連結: 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的響應並沒有被影響。

  Demo

  它也是另一種解釋方式:任務繁忙的主執行緒也不會影響在Worker上執行的動畫。所以即使主執行緒非常繁忙,你也可以通過此功能來避免掉幀並保證流暢的動畫:

  Demo

  上例展示了在普通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:

  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不多)的情況下,它都具有效能優勢。

 其它資源

相關文章