OffscreenCanvas - 使用 Web Worker 来加快画布操作

Tim Dresser

画布是一种很常用的工具 在屏幕上绘制各种图形,以及进入 WebGL 世界的入口点。 它可用于绘制形状、图片、运行动画,甚至显示和处理视频内容。 它通常用于在富媒体 Web 应用中打造出色的用户体验, 在线游戏。

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

同时,在现代网站中,脚本执行是最常见的 用户响应的来源。 由于画布逻辑和渲染与用户互动发生在同一线程上, 动画中涉及的(有时非常繁重的)计算可能会损害应用的真实性能 以及用户感知到的广告效果

幸运的是,OffscreenCanvas 是对这种威胁的回应

浏览器支持

  • 69
  • 79
  • 105
  • 16.4

来源

以前,画布绘制功能与 <canvas> 元素相关联, 这意味着它直接依赖于 DOM。顾名思义,OffscreenCanvas 通过将 DOM 和 Canvas API 移出屏幕来将 DOM 与 Canvas API 分离开来。

由于这种分离,OffscreenCanvas 的渲染与 DOM 完全分离, 因此与常规画布相比,它在速度方面有所提升, 。

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

在 worker 中使用 OffscreenCanvas

工作器 是 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 )

解锁主线程

将繁重的计算工作移至 Worker, 占用大量内存资源使用 transferControlToOffscreen 方法将常规画布镜像到 OffscreenCanvas 实例。应用于以下对象的操作: OffscreenCanvas 将自动呈现在源画布上。

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

在以下示例中,色彩主题发生变化时会进行大量计算,因此 即使是在速度较快的桌面设备上也要耗费几毫秒的时间。您可以选择在主线程上运行动画 还是在 worker 中。对于主线程,当内存占用量较大时,您无法与按钮进行交互 任务正在运行,则表明线程处于阻塞状态。对于工作器来说,对 界面响应速度。

演示

它的运行方式也相反:繁忙的主线程不会影响在 一个工作器。您可以使用此功能来避免视觉卡顿,并保证动画流畅 如以下演示所示。

演示

对于常规画布,当主线程人为过度工作时,动画会停止, 而基于 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 要求画布具有 style.widthstyle.height 属性。 OffscreenCanvas 与 DOM 完全分离,因此没有该类,因此您需要自行提供。 方法是将其打桩,或者提供将这些值与原始值关联起来的逻辑 画布尺寸。

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

演示

请注意,某些与 DOM 相关的 API 在 Worker 中并不直接可用,因此, 想要使用更高级的 Three.js 功能(例如纹理),则可能需要更多解决方法。 如需了解如何开始尝试这些功能,请参阅 2017 年 Google I/O 大会的视频

如果您大量使用画布的图形功能,则 OffscreenCanvas 对 从而影响应用的性能向 worker 提供画布渲染上下文会增加 并行处理,并更好地利用多核系统。

其他资源