文章轉(zhuǎn)載自眾成翻譯,原文鏈接https://www.zcfy.cc/article/offscreencanvas-speed-up-your-canvas-operations-with-a-web-worker
現(xiàn)在因?yàn)橛辛穗x屏Canvas,你可以不用在你的主線程中繪制圖像了!
Canvas 是一個(gè)非常受歡迎的表現(xiàn)方式,同時(shí)也是WebGL的入口。它能繪制圖形,圖片,展示動(dòng)畫,甚至是處理視頻內(nèi)容。它經(jīng)常被用來在富媒體web應(yīng)用中創(chuàng)建炫酷的用戶界面或者是制作在線(web)游戲。
它是非常靈活的,這意味著繪制在Canvas的內(nèi)容可以被編程。舉個(gè)??,JavaScript就提供了Canvas的系列API。這些給了Canvas非常好的靈活度。
但同時(shí),在一些現(xiàn)代化的web站點(diǎn),腳本解析運(yùn)行是實(shí)現(xiàn)流暢用戶反饋的最大的問題之一。因?yàn)镃anvas計(jì)算和渲染和用戶操作響應(yīng)都發(fā)生在同一個(gè)線程中,在動(dòng)畫中(有時(shí)候很耗時(shí))的計(jì)算操作將會(huì)導(dǎo)致App卡頓,降低用戶體驗(yàn)。
幸運(yùn)的是, OffscreenCanvas 離屏Canvas可以非常棒的解決這個(gè)麻煩!
到目前為止,Canvas的繪制功能都與<canvas>標(biāo)簽綁定在一起,這意味著Canvas API和DOM是耦合的。而OffscreenCanvas,正如它的名字一樣,通過將Canvas移出屏幕來解耦了DOM和Canvas API。
由于這種解耦,OffscreenCanvas的渲染與DOM完全分離了開來,并且比普通Canvas速度提升了一些,而這只是因?yàn)閮烧撸–anvas和DOM)之間沒有同步。但更重要的是,將兩者分離后,Canvas將可以在Web Worker中使用,即使在Web Worker中沒有DOM。這給Canvas提供了更多的可能性。
在Worker中使用OffscreenCanvas
Workers 是一個(gè)Web版的線程——它允許你在幕后運(yùn)行你的代碼。將你的一部分代碼放到Worker中可以給你的主線程更多的空閑時(shí)間,這可以提高你的用戶體驗(yàn)度。就像其沒有DOM一樣,直到現(xiàn)在,在Worker中都沒有Canvas API。
而OffscreenCanvas并不依賴DOM,所以在Worker中Canvas API可以被某種方法來代替。下面是我在Worker中用OffscreenCanvas來計(jì)算漸變顏色的??:
// 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 )
不要阻塞主線程
當(dāng)我們將大量的計(jì)算移到Worker中運(yùn)行時(shí),可以釋放主線程上的資源,這很有意思。我們可以使用transferControlToOffscreen 方法將常規(guī)的Canvas映射到OffscreenCanvas實(shí)例上。之后所有應(yīng)用于OffscreenCanvas的操作將自動(dòng)呈現(xiàn)在在源Canvas上。
const offscreen = document.querySelector('canvas').transferControlToOffscreen();
const worker = new Worker('myworkerurl.js');
worker.postMessage({ canvas: offscreen }, [offscreen]);
OffscreenCanvas 是 可轉(zhuǎn)移的](https://developer.mozilla.org/en-US/docs/Web/API/Transferable))。除了將其指定為傳遞信息中的字段之一以外,還需要將其作為postMessage(傳遞信息給Worker的方法)中的第二個(gè)參數(shù)傳遞出去,以便可以在Worker線程的context(上下文)中使用它。
在下面的??中,當(dāng)顏色主題發(fā)生變化時(shí)會(huì)發(fā)生“復(fù)雜的計(jì)算”,這個(gè)計(jì)算即使在高性能的臺(tái)式機(jī)上也要花費(fèi)幾毫秒。而你可以選擇在主線程或Worker上運(yùn)行這段動(dòng)畫。在主線程下,當(dāng)復(fù)雜計(jì)算開始運(yùn)行時(shí),你將無法與按鈕交互 - 線程被阻塞掉了。而在Worker下,UI的響應(yīng)并沒有被影響。
它也是另一種解釋方式:任務(wù)繁忙的主線程也不會(huì)影響在Worker上運(yùn)行的動(dòng)畫。所以即使主線程非常繁忙,你也可以通過此功能來避免掉幀并保證流暢的動(dòng)畫:
上例展示了在普通Canvas的下,當(dāng)主線程被添加繁忙任務(wù)時(shí)動(dòng)畫被阻塞了,而基于Worker的OffscreenCanvas播放卻很流利。
與流行庫一起使用
得益于OffscreenCanvas API一般情況下與常規(guī)Canvas元素的相API兼容,你可以很輕松地漸進(jìn)地使用它,也可以使用社區(qū)里的一些優(yōu)秀的圖形處理的庫/框架。
舉個(gè)??,你可以對(duì)其進(jìn)行特征檢測,如果可用的話,可通過在渲染的構(gòu)造函數(shù)中指定canvas的配置項(xiàng),然后實(shí)現(xiàn)與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尺寸相關(guān)聯(lián)的邏輯。
下面是一個(gè)運(yùn)行基本Three.js動(dòng)畫的demo:
但是請記住,有一些與DOM相關(guān)的API在Worker中并不容易獲得,因此如果你想使用更高級(jí)的Three.js功能(比如紋理)的話,可能需要更多變通的方法。有關(guān)這方面已經(jīng)開始嘗試的一些想法,請查看 Google I/O 2017的視頻。
此視頻的示例中出現(xiàn)的commit()方法我們并不推薦。請改用worker.requestAnimationFrame。
結(jié)論
如果你對(duì)圖像繪畫使用得非常多,OffscreenCanvas可以有效的提高你APP的性能。它使得Worker可以處理canvas的渲染繪制,讓你的APP更好地利用了多核系統(tǒng)。
OffscreenCanvas在Chrome 69中已經(jīng)不需要開啟flag(實(shí)驗(yàn)性功能)就可以使用了。它也正在被 Firefox 實(shí)現(xiàn)。由于其API與普通canvas元素非常相似,所以你可以輕松地對(duì)其進(jìn)行特征檢測并循序漸進(jìn)地使用它,而不會(huì)破壞現(xiàn)有的APP或庫的運(yùn)行邏輯。OffscreenCanvas在任何涉及到圖形計(jì)算以及動(dòng)畫表現(xiàn)且與DOM關(guān)系并不密切(即依賴DOM API不多)的情況下,它都具有性能優(yōu)勢。