昨天的範例中顯示當圖片的解析度很高時,執行 getImageData
會很久的問題 (我的電腦 Chrome 瀏覽器耗時 200~300ms 左右),但如果想將這段邏輯移到 Web worker
中處理,又會受限於 worker
無法直接對 DOM
元素進行操作的問題。
為了改善這種狀況,OffscreenCanvas 出現了,OffscreenCanvas
將常見用來操作 canvas
的 api (getImageData
, putImageData
等) 與 DOM
元素解耦,讓我們可以在 worker
中直接對 canvas
進行操作。
OffscreenCanvas
的使用主要分為兩種:
這個方式會利用 OffscreenCanvas.transferToImageBitmap 將 OffscreenCanvas
轉換為 ImageBitmap
,再將此 ImageBitmap
丟入 ImageBitmapRenderingContext.transferFromImageBitmap 渲染到另一個 canvas
中
const offscreen = new OffscreenCanvas(256, 256);
const context = offscreen.getContext('2d');
// 對 OffscreenCanvas 進行一些圖像操作
context.fillStyle = '#FF0000';
context.fillRect(0, 0, offscreen.width, offscreen.height);
// 將 OffscreenCanvas 轉換為 ImageBitmap
const bitmap = offscreen.transferToImageBitmap();
// 最終想要渲染圖像上去的 canvas
const targetCanvas = document.getElementById('canvas');
const targetContext = targetCanvas.getContext('bitmaprenderer');
// 傳入 OffscreenCanvas 的 ImageBitmap,最終渲染在 targetCanvas 上
targetContext.transferFromImageBitmap(bitmap);
以下範例由主線程發出訊號告知 worker
創建 OffscreenCanvas
,並在 worker
中繪製圖像,繪製完後再將 bitmap
傳回主線程的 canvas
並渲染在畫面上
// 主線程
const worker = new Worker('workers.js');
document.querySelector('button').addEventListener('click', () => {
worker.postMessage('create-canvas');
})
worker
線程接收到訊號後開始繪圖,繪圖完後利用 transferToImageBitmap
將 bitmap
轉移回主線程
// worker 線程
self.onmessage = () => {
const offscreen = new OffscreenCanvas(256, 256);
const context = offscreen.getContext("2d");
context.fillStyle = "#FF0000";
context.fillRect(0, 0, offscreen.width, offscreen.height);
const bitmap = offscreen.transferToImageBitmap();
self.postMessage({ bitmap }, [bitmap]);
};
主線程接收到 ImageBitmap
後,可以利用 transferFromImageBitmap
將 ImageBitmap
渲染到指定的 canvas
上
// 主線程
const worker = new Worker('workers.js');
worker.addEventListener('message', (e) => {
const { bitmap } = e.data;
const canvas = document.getElementById('canvas');
const context = canvas.getContext('bitmaprenderer');
// bitmap 的圖像資訊將會被渲染到 canvas 上
context.transferFromImageBitmap(bitmap);
});
第二個方式則是利用 canvas.transferControlToOffscreen,將原本的 canvas
轉換為 OffscreenCanvas
,然後這個 OffscreenCanvas
就可以被轉移到 worker
中繪圖操作,這個方法相較於第一個方式較為直覺,也比較常用
const canvas = document.getElementById("canvas");
const offscreen = canvas.transferControlToOffscreen();
const worker = new Worker("worker.js");
worker.postMessage({ canvas: offscreen }, [offscreen]);
以下範例調用 canvas.transferControlToOffscreen
函數在 Web worker
中操作 canvas
首先主線程調用 canvas.transferControlToOffscreen
後,可以將 OffscreenCanvas
的所有權轉移到 worker
線程
// 主線程
const worker = new Worker('workers.js');
document.querySelector('button').addEventListener('click', () => {
const canvas = document.getElementById('canvas');
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);
})
接著 worker
線程就可以直接用一般 canvas
常見的方法操作圖像,而這裡的操作也會直接反映在原本主線程的 canvas
上
// worker 線程
self.onmessage = (e) => {
const { canvas } = e.data;
const context = canvas.getContext("2d");
context.fillStyle = "#FF0000";
context.fillRect(0, 0, canvas.width, canvas.height);
};
利用 OffscreenCanvas
可以解耦 canvas
操作圖像與 DOM
之間的關係,因此可以在 worker
線程中操作圖像,改善了原本在主線程中進行耗時的 canvas
操作造成 UI 渲染阻塞問題。