OffscreenCanvas - 使用Web Worker加速您的Canvas操作

现在,您可以使用 OffscreenCanvas 从主线程渲染图形!

Canvas 是一种在屏幕上绘制各种图形的流行方式,也是 WebGL 世界的切入点。它可用于绘制形状,图像,运行动画,甚至显示和处理视频内容。它通常用于在富媒体网络应用程序和在线游戏中创建出色的用户体验。

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

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

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

到目前为止,画布绘制功能与<canvas>元素绑定,这意味着它直接取决于 DOM。顾名思义,OffscreenCanvas 通过将其移出屏幕来解耦 DOM 和 Canvas API。

由于这种解耦,OffscreenCanvas 的渲染与 DOM 完全分离,因此在常规画布上提供了一些速度提升,因为两者之间没有同步。但更重要的是,它可以在 Web Worker 中使用,即使没有可用的 DOM。这可以实现各种有趣的用例。

在 Worker 中使用 OffscreenCanvas

Worker 是网络的线程版本 - 它们允许您在后台运行任务。将一些脚本移动到 Worker 可以为应用程序提供更多空间,以便在主线程上执行用户关键任务。到目前为止,没有办法在 Worker 中使用 Canvas API,因为没有可用的 DOM。

OffscreenCanvas 不依赖于 DOM,因此可以使用它。在这里,我使用 OffscreenCanvas 来计算 worker 中的渐变颜色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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[])`;
}

getGradientColor(40); // rgba(152, 0, 104, 255 )

取消阻止主线程

将大量计算移动到 Worker 时,可以释放主线程上的大量资源,这会变得更加有趣。我们可以使用 transferControlToOffscreen 方法将常规画布镜像到 OffscreenCanvas 实例。应用于 OffscreenCanvas 的操作将自动在源画布上呈现。

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

OffscreenCanvas 是可转让的。除了将其指定为消息中的字段之外,还需要将其作为 postMessage(转移)中的第二个参数传递,以便可以在 Worker 上下文中使用它。

在下面的示例中,当颜色主题发生变化时会发生“繁重计算” - 即使在快速桌面上也应该花费几毫秒。您可以选择在主线程或 Worker 线程上运行动画。在主线程的情况下,当繁重的任务运行时,您无法与按钮交互 - 线程被阻止。对于 Worker,对 UI 响应性没有影响。

示例:keep-ui-responsive

它也是另一种方式:繁忙的主线程不会影响在 worker 上运行的动画。尽管有主要的线程流量,您仍可以使用此功能来避免视觉抖动并保证流畅的动画:

示例

在常规画布的情况下,当主线程被人为地过度工作时动画停止,而基于 Worker 的 OffscreenCanvas 播放顺利。

与流行的库一起使用

由于 OffscreenCanvas API 通常与常规 Canvas 元素兼容,因此您可以轻松地将其用作渐进增强功能,也可以使用市场上的一些领先图形库。

例如,您可以对其进行功能检测,如果可用,通过在渲染器构造函数中指定 canvas 选项将其与 Three.js 一起使用:

1
2
3
4
5
6
7
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 动画的演示:

示例:use-with-lib

请记住,某些与 DOM 相关的 API 在 Worker 中并不容易获得,因此如果您想使用更高级的 Three.js 功能(如纹理),则可能需要更多变通方法。

结论

如果您大量使用画布的图形功能,OffscreenCanvas 可以积极影响您的应用程序的性能。使工作人员可以使用画布渲染上下文增加了 Web 应用程序的并行性,并更好地利用了多核系统。

OffscreenCanvas 在 Chrome 69 中没有标记。它也在 Firefox 中开发。由于其 API 与常规 canvas 元素非常一致,因此您可以轻松地对其进行特征检测并将其用作渐进增强,而不会破坏现有的应用程序或库逻辑。它在图形和动画与画布周围的 DOM 紧密相关的所有情况下都具有性能优势。

0%