[Canvas优化实验] 离屏画布能带来多大的性能优化?

使用场景

当画面存在重复绘制的多个元素时,将画面上的元素全部绘制到一个离屏的Canvas上,再一次绘制到主canvas上,能够大幅节省绘制时间,优化渲染速度。 但是具体能快多少呢,这篇文章来测试一下

测试场景

我们准备了一块可以平移缩放的画布, 画布背景是通过一次path完成的绘制,在每次画面中更新,没有任何重用,单次渲染为0.0~0.1ms,这是Performance的测量极限,几乎不耗时

生成10000个矩形,随机绘制到坐标10000*10000的画布上,最终绘制效果如下

在在线绘制中,我们过滤一些完全位于屏幕之外的元素,其余绘制到屏幕上,代码如下

world2local 是外部提供的将世界坐标映射到屏幕坐标到工具函数

javascript 复制代码
const MapRange = 10000
const Size = 100
const randomCoords = new Array(10000).fill(0)
  .map(() => [Math.random() * MapRange, Math.random() * MapRange])
  .forEach(rect => {
    const [x, y] = world2local(rect[0], rect[1])
    if (!(x <= -Size * scale || y <= -Size * scale || x >= canvas.width || y >= canvas.height)) {
      ctx.fillRect(x, y, Size * scale, Size * scale);
    }
  })

绘制时间如下,可以看到,画面中多绘制这一项内容会导致单次渲染耗时增加约3ms

在离线绘制中,我们创建一个canvas,然后将全部元素绘制到一个新创建的canvas上,在每次更新的时候将这个canvas上的内容复制到主屏幕上

javascript 复制代码
const MapRange = 10000
const randomCoords = new Array(10000).fill(0).map(() => [Math.random() * MapRange, Math.random() * MapRange])
const offscreenCanvas = document.createElement('canvas')
const Size = 100
const offscreenCtx = offscreenCanvas.getContext('2d')
function drawOffscreen() {
  offscreenCanvas.width = (MapRange + Size) * scale
  offscreenCanvas.height = (MapRange + Size) * scale
  offscreenCtx.clearRect(0, 0, offscreenCanvas.width, offscreenCanvas.height);
  randomCoords.forEach(rect => {
    offscreenCtx.fillRect(rect[0] * scale, rect[1] * scale, Size * scale, Size * scale)
  })
}
// 每次缩放需要重新生成对应缩放大小的画布
drawOffscreen()


// 每次平移时只需执行
const [x, y] = world2local(0, 0)
ctx.drawImage(offscreenCanvas, x, y);

绘制时间如下,可以看到,平移画布时单次渲染的成本几乎与空画布相同, 缩放时重新生成离线画布,因此没有优化

结论

离屏Canvas能大幅提高canvas渲染速度,使用离线绘制之后单次重绘成本可以和空画布持平 但是在初次生成的时候仍有较大性能负担,针对一些仅限平移的场景,重绘成本可以接近0

  1. offscreenCanvas的大小是 原始大小*屏幕缩放,如果画布放大到一个很大的值,想要绘制全部元素到画布上,会创建一个巨大的画布,此时内存占用和重绘时间都达到一个不可接受的值,可以采用以下方案。
  • 设定一个最大值裁切画面,只渲染屏幕以及周边一定范围的数据,平移之后再重新渲染,重用画面中重复的范围
  • 分块Canvas,将数据分别绘制到多块Canvas上,只有屏幕画布移动到附近才提前绘制附近的Canvas块
  1. offscreenCanvas在画布缩放时需要更高的成本重绘(因为无法过滤屏幕外数据),实际上可以对在drawImage的时候添加缩放,临时使用缩放前的数据渲染到屏幕上,然后延时更新数据,可以结合以下方法一起更新。
  • 在一个防抖后的函数中执行重绘
  • 使用一个调度器分段执行重绘,完全重绘之后再替换数据
  • 缓冲绘制,通过更多offscreenCanvas提前绘制好多个LOD,缩放时直接替换

完整代码

完整代码可以在我的Github上找到,欢迎学习

相关推荐
蛋蛋_dandan1 天前
Fabric.js从0到1实现图片框选功能
canvas
wayhome在哪3 天前
用 fabric.js 搞定电子签名拖拽合成图片
javascript·产品·canvas
德育处主任3 天前
p5.js 掌握圆锥体 cone
前端·数据可视化·canvas
德育处主任4 天前
p5.js 3D 形状 "预制工厂"——buildGeometry ()
前端·javascript·canvas
德育处主任6 天前
p5.js 3D盒子的基础用法
前端·数据可视化·canvas
掘金安东尼6 天前
2分钟创建一个“不依赖任何外部库”的粒子动画背景
前端·面试·canvas
百万蹄蹄向前冲7 天前
让AI写2D格斗游戏,坏了我成测试了
前端·canvas·trae
用户2519162427119 天前
Canvas之画图板
前端·javascript·canvas
FogLetter12 天前
玩转Canvas:从静态图像到动态动画的奇妙之旅
前端·canvas
用户25191624271113 天前
Canvas之贪吃蛇
前端·javascript·canvas