Canvas 离屏绘制与实践

1,007 阅读3分钟

前言

前段时间有在开发小程序的图片处理需求,用到了wx.createOffscreenCanvas,可以直接创建canvas实例来进行画布的绘制,我当时很惊讶,好奇 Web 没有这个能力吗,因为小程序的 Canvas 标准是根据 Web 标准升级来的,于是我在MDN查了一下,发现了这个能力,但是当前属于实验阶段。(不能上生产)

截屏2024-08-18 16.01.29.png

在 Web 中使用 OffscreenCanvas

这个能力主要是为了将耗时渲染移出主线程,所以并没有完全像小程序一样,直接通过wx.createOffscreenCanvas来完全不使用<Canvas>来进行画布绘制,搓了一个示例代码

<!DOCTYPE html>
<html>
  <body>
    <canvas id="canvas" width="800" height="600"></canvas>
  </body>
  <script>
    // 主线程创建 Canvas 实例
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('bitmaprenderer');
​
    // 创建 OffscreenCanvas 实例
    const offscreenCanvas = new OffscreenCanvas(800, 600);
​
    // 在 Worker 线程中绘制图形
    const worker = new Worker('./work.js');
    worker.postMessage({ canvas: offscreenCanvas }, [offscreenCanvas]);
​
    // 将 OffscreenCanvas 的渲染结果绘制到 Canvas 上
    worker.onmessage = function (e) {
      ctx.transferFromImageBitmap(e.data.imageBitmap);
    };
  </script>
</html>
// ./work.js
onmessage = function (e) {
  const canvas = e.data.canvas;
  const ctx = canvas.getContext('2d');
  ctx.fillStyle = 'red';
  ctx.fillRect(10, 10, 100, 100);
  const imageBitmap = canvas.transferToImageBitmap();
  postMessage({ imageBitmap });
};
  • 在主线程中根据canvas元素创建了一个画布实例,同时创建了一个offscreenCanvas离屏实例,同时向work线程中传入我的offscreenCanvas实例。
  • 同时worker线程接收到实例后执行我的绘制操作,再将imageBitmap传到主线程,主线程的canvas实例通过transferFromImageBitmap将内容填充到画布上,完成整个绘制流程

这里你可能会有几个疑问:

  1. getContext('bitmaprenderer');的参数是bitmaprenderer
  2. 为什么要这么麻烦,将绘制流程转到worker线程中

接下来我继续给大家介绍

大家对canvas.getContext这个函数的参数可能对2dwebgl比较熟悉,如果传入bitmaprenderer,那么将创建一个只可以传入ImageBitmap类型内容的画布上下文。

OffscreenCanvastransferToImageBitmap返回的内容为ImageBitmap,所以结合getContext('bitmaprenderer')使用,也当然按需要也可以使用OffscreenCanvas.convertToBlob(),这个转换能力是异步的,所以可以:

onmessage = function (e) {
  const canvas = e.data.canvas;
  const ctx = canvas.getContext('2d');
  ctx.fillStyle = 'red';
  ctx.fillRect(10, 10, 100, 100);
  const imageBitmap = canvas.transferToImageBitmap();
  canvas.convertToBlob().then((blob) => {
    postMessage({ imageBitmap, blob });
  });
};
​

在拿到图像的blob后一同传入主线程,如果有对图像blob内容的需要,在这里即可完成转换,如果有即时绘制的要求也可以使用ImageBitmap.

web worker

我们放在worker内的代码将会放在单独的线程内,也就是我们可以在里面执行需要放在后台去执行的内容,来减少主线程的压力。

主线程和worker线程使用postmessage进行通信,比如上文的代码,那为什么我非要使用OffscreenCanvas来进行画布的处理呢?

因为worker线程有一个需要注意的事情,那就是无法在worker内操作DOM元素,所以我们需要借助OffscreenCanvas的能力结合worker来实现真正的离屏绘制。

最后

目前这个能力还处在实验阶段,相信过不了多久我们就可以使用这个能力,来优化我们的绘制性能。