Tl;DR:html
產生的契機:用戶在交互時的Canvas邏輯與渲染在同一線程內執行,動畫產生的卡頓可能會影響用戶體驗。若在後臺渲染,則能夠避免耗時的渲染任務阻塞主線程。webpack
使用OffscreenCanvas與Worker結合的方式能夠將渲染任務放在子線程中,有效提高用戶交互時的界面流暢度。git
Transfer模式與Control模式。github
本身起的名字,參考了這篇文章。web
worker線程算法
let offscreen = new OffscreenCanvas(w,h);
let ctx = offscreen.getContext('2d');
// 一些渲染操做...
let image = offscreen.transferToImageBitmap();
self.postMessage({ image }, [image]);
複製代碼
主線程chrome
renderWorker.onmessage = msg => {
let imageBuffer = msg.data.image;
let bitmapContext = canvas.getContext("bitmaprenderer");
bitmapContext.transferFromImageBitmap(imageBuffer);
}
複製代碼
這種方式能夠用於H5遊戲的精靈加載,文本渲染、生成海報等固定的渲染任務。canvas
在該模式下不須要transfer的相關操做,內部直接對綁定的dom元素進行更新。dom
主線程異步
const offscreen = document.querySelector('canvas').transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);
複製代碼
worker線程
onmessage = e => {
let canvas = e.data.canvas
let ctx = canvas.getContext('2d');
// 一些渲染操做...
// 這裏的渲染操做會在Canvas元素上同步繪製的圖像
}
複製代碼
在使用webpack進行構建的項目中,對於worker文件須要進行一些額外的處理。
目前生態中主要有三種處理web worker的loader: worker-loader,workerize-loader和comlink-loader
關於這幾種loader的介紹能夠看看這篇後面的loader部分。
經測試,因爲workerize-loader
目前沒有對postMessage方法中的Transferable參數序列進行處理,所以沒法將主線程的OffscreenCanvas對象傳入worker中,詳情見這個issue,worker-loader
表現正常
在worker中使用圖像數據與主線程中有所不一樣。
主線程
const loadImage = imgPath => {
return new Promise((resolve, reject) => {
let img = new Image();
img.setAttribute("crossOrigin", "anonymous"); // to solve "Tainted canvases may not be exported" error
img.onload = () => { resolve(img); };
img.onerror = e => { reject(new Error(e));};
img.src = imgPath;
});
};
const image = await loadImage(url)
// use image...
複製代碼
worker線程
const response = await fetch(url);
const blob = await response.blob();
const image = await createImageBitmap(blob);
// use image...
複製代碼
在worker線程中執行動畫有兩種方式:
瞭解了OffscreenCanvas與worker的相關特性,不如動手嘗試一下,以一個蒙版合成的渲染任務爲例。
/* 0. 獲取Canvas元素 */
let mainCanvas = document.querySelector('#canvas');
let mainCtx = mainCanvas.getContext('2d');
/* 1. 準備Image對象 */
let img = await loadImage(imgPath);
...
/* 2. 建立一個Canvas來合成結果圖像 */
let maskLayer = document.createElement("canvas");
maskLayer.width = width;
maskLayer.height = height;
const maskCtx = maskLayer.getContext("2d");
maskCtx.drawImage(img, 0, 0);
let maskData = maskCtx.getImageData(0, 0, width, height);
for (let i = 0; i < width * height; i++) {
if (values[i] !== 255) {
maskData.data[(i + 1) * 4 - 1] = mask[i];
}
}
maskCtx.putImageData(maskData, 0, 0);
...
/* 3. 繪製到Canvas元素上 */
mainCtx.drawImage(maskLayer, 0, 0);
複製代碼
主線程
import CanvasWorker from "worker-loader!@/workers/canvas.worker.js";
...
/* 0. 獲取Canvas元素 */
let canvas = document.querySelector('#canvas');
let offscreenCanvas = canvas.transferControlToOffscreen();
let canvasWorker = new CanvasWorker();
// 將綁定的offscreenCanvas實例傳遞到worker線程中
canvasWorker.postMessage({ canvas: offscreenCanvas, event: "init" }, [offscreenCanvas]);
...
/* 2.發送繪製事件 */
let img = this.resultImg || this.img;
canvasWorker.postMessage({
event: "draw"
payload: JSON.stringify({ width, height, imgSrc, mask }) // 因爲結構化克隆算法的限制,這裏對參數對象進行JSON序列化後賦值
});
複製代碼
worker線程
let canvas, ctx;
onmessage = async e => {
const data = e.data;
const { payload, event } = data;
switch (event) {
case "init": {
// 保存傳入的OffscreenCanvas實例
canvas = data.canvas;
ctx = canvas.getContext("2d");
break;
}
case "draw": {
/* 0. 解析參數 */
let { width, height, mask } = JSON.parse(payload);
...
/* 1. 下載圖片並獲取的ImageBitmap數據 */
const response = await fetch(imgSrc);
const blob = await response.blob();
const imageBitmap = await createImageBitmap(blob);
...
/* 2. 建立一個新的OffscreenCanvas來合成結果圖像 */
let maskLayer = new OffscreenCanvas(width, height);
const maskCtx = maskLayer.getContext("2d");
maskCtx.drawImage(imageBitmap, 0, 0); // 使用ImageBitMap繪製圖片
let maskData = maskCtx.getImageData(0, 0, width, height);
for (let i = 0; i < width * height; i++) {
if (values[i] !== 255) {
maskData.data[(i + 1) * 4 - 1] = mask[i];
}
}
maskCtx.putImageData(maskData, 0, 0);
...
/* 3. Canvas元素上會更新繪製效果 */
ctx.drawImage(maskLayer, 0, 0);
break;
}
}
};
複製代碼
極簡Chromium渲染流水線:
blink ---(Main Frame)---> Layer Compositor ---(Compositor Frame)---> Display Compositor ---(GL/UI Frame)---> Window
blink中offscreen_canvas與html_canvas_element的主類源碼:
OffscreenCanvas
與HTMLCanvasElement
共同繼承的類:
the base class for all elements that can host a rendering context
,包含通用的數據轉換、尺寸設置、屬性獲取等方法