iT邦幫忙

2023 iThome 鐵人賽

DAY 16
0

昨天的範例中顯示當圖片的解析度很高時,執行 getImageData 會很久的問題 (我的電腦 Chrome 瀏覽器耗時 200~300ms 左右),但如果想將這段邏輯移到 Web worker 中處理,又會受限於 worker 無法直接對 DOM 元素進行操作的問題。
為了改善這種狀況,OffscreenCanvas 出現了,OffscreenCanvas 將常見用來操作 canvas 的 api (getImageData, putImageData 等) 與 DOM 元素解耦,讓我們可以在 worker 中直接對 canvas 進行操作。

OffscreenCanvas 的使用主要分為兩種:

1. 對 canvas 中的 ImageBitmap 進行轉換操作

這個方式會利用 OffscreenCanvas.transferToImageBitmapOffscreenCanvas 轉換為 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 並渲染在畫面上

範例 Demo

發出訊號告知 worker 線程繪圖

// 主線程
const worker = new Worker('workers.js');
document.querySelector('button').addEventListener('click', () => {
  worker.postMessage('create-canvas');
})

worker 線程利用 OffscreenCanvas 繪圖

worker 線程接收到訊號後開始繪圖,繪圖完後利用 transferToImageBitmapbitmap 轉移回主線程

// 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 後渲染在畫面上

主線程接收到 ImageBitmap 後,可以利用 transferFromImageBitmapImageBitmap 渲染到指定的 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);
});

2. 將 canvas 轉換為 OffscreenCanvas

第二個方式則是利用 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

範例 Demo

首先主線程調用 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 渲染阻塞問題。

Reference

OffscreenCanvas


上一篇
在 Web worker 中操作圖像的 ImageData
下一篇
在 Web worker 中使用 OffscreenCanvas
系列文
網頁的另一個大腦:從基礎到進階掌握 Web Worker 技術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言