画布是一种在屏幕上绘制各种图形的常用方法,也是 WebGL 世界的入口点。它可用于绘制形状、图片、运行动画,甚至显示和处理视频内容。它通常用于在富媒体 Web 应用和在线游戏中打造出色的用户体验。
它支持脚本化,这意味着您可以通过编程方式(例如使用 JavaScript)创建在画布上绘制的内容。这让画布具有极大的灵活性。
与此同时,在现代网站中,脚本执行是导致用户响应速度缓慢问题的最常见原因之一。由于画布逻辑和渲染会在与用户互动相同的线程中进行,因此动画涉及的计算(有时非常繁重)可能会影响应用的实际和感知性能。
幸运的是,OffscreenCanvas 就是为应对此威胁而推出的。
以前,画布绘制功能与 <canvas>
元素相关联,这意味着它直接依赖于 DOM。正如其名所示,OffscreenCanvas 会通过将其移出屏幕来解耦 DOM 和 Canvas API。
得益于这种分离,OffscreenCanvas 的渲染完全与 DOM 分离,因此与常规画布相比,速度有所提升,因为二者之间没有同步。
不过,更重要的是,即使没有 DOM 可用,它也可以在 Web Worker 中使用。这支持各种有趣的用例。
在 worker 中使用 OffscreenCanvas
Worker 是 Web 版线程,可让您在后台运行任务。
将部分脚本移至工作器可让应用有更多空间在主线程上执行对用户至关重要的任务。没有 OffscreenCanvas,就无法在 worker 中使用 Canvas API,因为没有可用的 DOM。
OffscreenCanvas 不依赖于 DOM,因此可以使用。以下示例使用 OffscreenCanvas 在 worker 中计算渐变颜色:
// file: worker.js
function getGradientColor(percent) {
const canvas = new OffscreenCanvas(100, 1);
const ctx = canvas.getContext('2d');
const gradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'blue');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, ctx.canvas.width, 1);
const imgd = ctx.getImageData(0, 0, ctx.canvas.width, 1);
const colors = imgd.data.slice(percent * 4, percent * 4 + 4);
return `rgba(${colors[0]}, ${colors[1]}, ${colors[2]}, ${colors[3]})`;
}
getGradientColor(40); // rgba(152, 0, 104, 255 )
取消阻塞主线程
将繁重计算移至工作器可释放主线程上的大量资源。使用 transferControlToOffscreen 方法将常规画布镜像到 OffscreenCanvas 实例。应用于 OffscreenCanvas 的操作将自动渲染在源画布上。
const offscreen = document.querySelector('canvas').transferControlToOffscreen();
const worker = new Worker('myworkerurl.js');
worker.postMessage({canvas: offscreen}, [offscreen]);
在以下示例中,在颜色主题发生变化时会发生大量计算,即使在快速桌面设备上,也需要几毫秒的时间。您可以选择在主线程或工作器中运行动画。对于主线程,在运行繁重任务时,您无法与按钮互动,因为线程处于阻塞状态。对于 worker,对界面响应能力没有影响。
反之亦然:繁忙的主线程不会影响在工作器上运行的动画。您可以使用此功能来避免视觉卡顿,并确保即使有主线程流量,动画也能流畅运行,如以下演示所示。
对于常规画布,当主线程被人为过度使用时,动画会停止,而基于工作器的 OffscreenCanvas 会流畅播放。
与热门库搭配使用
由于 OffscreenCanvas API 通常与常规 Canvas 元素兼容,因此您可以将其用作渐进式增强功能,还可以与市场上的一些领先图形库搭配使用。
例如,您可以通过在渲染程序构造函数中指定画布选项来检测它,并在可用时将其与 Three.js 搭配使用:
const canvasEl = document.querySelector('canvas');
const canvas =
'OffscreenCanvas' in window
? canvasEl.transferControlToOffscreen()
: canvasEl;
canvas.style = {width: 0, height: 0};
const renderer = new THREE.WebGLRenderer({canvas: canvas});
这里有一个要注意的地方,即 Three.js 希望 canvas 具有 style.width
和 style.height
属性。OffscreenCanvas 与 DOM 完全分离,因此不具有此属性,因此您需要自行提供此属性,方法是通过桩或提供将这些值与原始画布尺寸相关联的逻辑。
以下代码展示了如何在 worker 中运行基本 Three.js 动画:
请注意,某些与 DOM 相关的 API 无法在 worker 中直接使用,因此,如果您想使用纹理等更高级的 Three.js 功能,可能需要更多权宜解决方法。如需了解如何开始尝试这些功能,请观看 2017 年 Google I/O 大会的视频。
如果您大量使用画布的图形功能,OffscreenCanvas 可以对应用的性能产生积极影响。向 worker 提供画布渲染上下文可提高 Web 应用中的并行性,并更好地利用多核系统。