Canvas 是一种在屏幕上绘制各种图形的常用方法,也是进入 WebGL 世界的入口。它可用于绘制形状、图像、运行动画,甚至显示和处理视频内容。它通常用于在富媒体 Web 应用程序和在线游戏中创建精美的用户体验。
它是可脚本化的,这意味着在 canvas 上绘制的内容可以通过编程方式创建,例如,使用 JavaScript。这赋予了 canvas 极大的灵活性。
与此同时,在现代网站中,脚本执行是最常见的 用户响应问题 来源之一。由于 canvas 逻辑和渲染与用户交互发生在同一线程上,因此动画中涉及的(有时是繁重的)计算可能会损害应用程序的实际和感知性能。
幸运的是,OffscreenCanvas 正是为了应对这一威胁而生的。
以前,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.width
和 style.height
属性。OffscreenCanvas 由于完全脱离 DOM,因此没有这些属性,因此你需要自己提供,可以通过存根或提供将这些值与原始 canvas 尺寸联系起来的逻辑。
以下展示了如何在 worker 中运行基本的 Three.js 动画
请记住,一些与 DOM 相关的 API 在 worker 中不易使用,因此如果你想使用更高级的 Three.js 功能(如纹理),你可能需要更多的解决方法。有关如何开始尝试这些功能的想法,请观看 Google I/O 2017 的视频。
如果你大量使用 canvas 的图形功能,OffscreenCanvas 可以积极影响你的应用程序性能。使 canvas 渲染上下文可用于 worker 可以提高 Web 应用程序的并行性,并更好地利用多核系统。