OffscreenCanvas—使用 Web Worker 加速你的 Canvas 操作

Canvas 是一种在屏幕上绘制各种图形的常用方法,也是进入 WebGL 世界的入口。它可用于绘制形状、图像、运行动画,甚至显示和处理视频内容。它通常用于在富媒体 Web 应用程序和在线游戏中创建精美的用户体验。

它是可脚本化的,这意味着在 canvas 上绘制的内容可以通过编程方式创建,例如,使用 JavaScript。这赋予了 canvas 极大的灵活性。

与此同时,在现代网站中,脚本执行是最常见的 用户响应问题 来源之一。由于 canvas 逻辑和渲染与用户交互发生在同一线程上,因此动画中涉及的(有时是繁重的)计算可能会损害应用程序的实际和感知性能。

幸运的是,OffscreenCanvas 正是为了应对这一威胁而生的。

浏览器支持

  • Chrome: 69.
  • Edge: 79.
  • Firefox: 105.
  • Safari: 16.4.

来源

以前,canvas 绘图功能与 <canvas> 元素绑定,这意味着它直接依赖于 DOM。OffscreenCanvas,顾名思义,通过将其移出屏幕来解耦 DOM 和 Canvas API。

由于这种解耦,OffscreenCanvas 的渲染完全独立于 DOM,因此与常规 canvas 相比,它提供了一些速度上的改进,因为两者之间没有同步。

更重要的是,即使没有 DOM 可用,它也可以在 Web Worker 中使用。这实现了各种有趣的用例。

在 Worker 中使用 OffscreenCanvas

Worker 是 web 版本的线程——它们允许你在后台运行任务。

将一些脚本移至 worker 可以为你的应用程序在主线程上执行用户关键任务提供更多空间。在没有 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 )

解除主线程阻塞

将繁重的计算移至 worker 可以让你在主线程上释放大量资源。使用 transferControlToOffscreen 方法将常规 canvas 镜像到 OffscreenCanvas 实例。应用于 OffscreenCanvas 的操作将自动在源 canvas 上渲染。

const offscreen = document.querySelector('canvas').transferControlToOffscreen();
const worker = new Worker('myworkerurl.js');
worker.postMessage({canvas: offscreen}, [offscreen]);

在以下示例中,繁重的计算发生在颜色主题更改时——即使在快速台式机上,也应该需要几毫秒。你可以选择在主线程或 worker 中运行动画。如果是在主线程中,则在繁重任务运行时,你无法与按钮交互——线程被阻塞。如果是 worker,则不会影响 UI 响应性。

演示

反过来也是如此:繁忙的主线程不会影响在 worker 上运行的动画。你可以使用此功能来避免视觉卡顿,并保证动画即使在主线程繁忙的情况下也能流畅运行,如下面的演示所示。

演示

对于常规 canvas,当主线程人为地过载时,动画会停止,而基于 worker 的 OffscreenCanvas 则可以流畅播放。

由于 OffscreenCanvas API 通常与常规 Canvas Element 兼容,因此你可以将其用作渐进增强,也可以与市场上一些领先的图形库一起使用。

例如,你可以进行功能检测,如果可用,则可以通过在渲染器构造函数中指定 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.widthstyle.height 属性。OffscreenCanvas 由于完全脱离 DOM,因此没有这些属性,因此你需要自己提供,可以通过存根或提供将这些值与原始 canvas 尺寸联系起来的逻辑。

以下展示了如何在 worker 中运行基本的 Three.js 动画

演示

请记住,一些与 DOM 相关的 API 在 worker 中不易使用,因此如果你想使用更高级的 Three.js 功能(如纹理),你可能需要更多的解决方法。有关如何开始尝试这些功能的想法,请观看 Google I/O 2017 的视频

如果你大量使用 canvas 的图形功能,OffscreenCanvas 可以积极影响你的应用程序性能。使 canvas 渲染上下文可用于 worker 可以提高 Web 应用程序的并行性,并更好地利用多核系统。

其他资源