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
,包含通用的数据转换、尺寸设置、属性获取等方法