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