昨天學習到使用 OffscreenCanvas 在 Web worker 中操作圖像,避免主線程處理 canvas 的耗時操作導致畫面卡頓,今天打算繼續優化前兩天寫的程式,改用 OffscreenCanvas 實作來看是否能提升效能
目的
worker 中使用 OffscreenCanvas,希望藉此來進一步增進效能說明
worker 處理已經不會讓主線程的動畫卡頓,但當圖片畫質過高,取得圖片像素(getImageData) 所耗費的時間有可能達到 100~200ms,此時還是一樣會造成動畫卡頓OffscreenCanvas,將 取得圖片像素(getImageData) 的邏輯也移到 worker 中處理。希望最後驗證不論圖片的畫質多高,只要將全部操作圖像的邏輯移到 worker 中執行,都不會阻塞主線程OffscreenCanvas 於 worker 線程中處理
首先新增了一個 使用 worker (OffscreenCanvas) 按鈕,按下去後會產生 OffscreenCanvas ,並將 OffscreenCanvas 傳遞到 worker
// 主線程
const offscreenCanvasWorker = new Worker("offscreen-canvas-worker.js");
const canvas = document.createElement("canvas");
const offscreen = canvas.transferControlToOffscreen();
// 將 OffscreenCanvas 傳遞到 worker
offscreenCanvasWorker.postMessage(
{ canvas: offscreen, imageUrl },
[offscreen]
);
由於 worker 中無法控制 DOM 元素,因此原本使用的 new Image() 加載圖片的方式變得不可行,所以改用 fetch 的方式將圖片從網路拿回來,並轉成之後需要用到的 blob 格式
// worker 線程
const fetchImageBlob = async (imageUrl) => {
const response = await fetch(imageUrl);
const imageBlob = await response.blob();
return imageBlob;
};
接著將圖片從 blob 轉為 ImageBitmap 型別,以供下一步的 drawImage 使用
// worker 線程
const bitmap = await createImageBitmap(imageBlob);
將以上準備好的 ImageBitmap 繪製到主線程傳來的 canvas 上
// worker 線程
// canvas 為從主線程傳來的 OffscreenCanvas
const ctx = canvas.getContext("2d");
const width = bitmap.width;
const height = bitmap.height;
canvas.width = width;
canvas.height = height;
ctx.drawImage(bitmap, 0, 0, width, height);
接下來的步驟就跟原先的一樣,先取得 ImageData 後再執行圖像外框透明化的像素操作,最後利用 putImageData 將 ImageData 更新回 OffscreenCanvas
// worker 線程
const imageData = ctx.getImageData(0, 0, width, height);
const newImageData = makeImageDataTransparent(imageData, option);
ctx.putImageData(newImageData, 0, 0);
到目前為止,worker 線程中的 canvas 都處理完操作圖像的邏輯了,由於 worker 中的 canvas 是從主線程傳來的,所以最後只需要將主線程一開始傳入的 canvas 渲染在畫面上
// 主線程
const offscreenCanvasWorker = new Worker("offscreen-canvas-worker.js");
const canvas = document.createElement("canvas");
const offscreen = canvas.transferControlToOffscreen();
offscreenCanvasWorker.postMessage(
{ canvas: offscreen, imageUrl },
[offscreen]
);
offscreenCanvasWorker.onmessage = () => {
// 將 worker 處理過的 canvas 渲染在畫面上
renderCanvas(canvas);
};

OffscreenCanvas 來改善效能,但似乎沒有想像中的順利,可以發現按下按鈕後,紅色方塊動畫一樣有卡頓的現象,看來瓶頸一樣是當執行 取得圖片像素(getImageData) 時會佔用到主線程的資源,推測原因可能是 canvas 一開始就是由主線程傳到 worker 線程,而在 worker 線程呼叫 getImageData 時,主線程一樣需要資源把任何操作圖像的邏輯回寫到主線程的 canvas 上worker 中處理,還是能夠增進效能,避免主線程的動畫卡頓今天的範例中用到了 createImageBitmap,將 blob 轉換為 ImageBitmap 型態的資料,但 Safari 似乎還不支援以 blob 參數傳入的方式,我實測在 Safari 中執行圖片會跑不出來,但也沒有錯誤訊息出現
// Safari 似乎無法正常處理傳入 blob 格式的資料
const bitmap = await createImageBitmap(imageBlob);
Loading Images with Web Workers