Canvas 海量粒子瞬时渲染及动态改变的优化方案
最近业务上需要制作一个星云图来展示一些实时数据,像这样:
涉及 SVG 绘图、Canvas 等技术,其中使用 Canvas 瞬时渲染百万级别的粒子及动态去改变这些粒子是整个项目的核心所在。
所以,如何优化海量粒子的瞬时渲染及动态改变这些粒子就非常非常的关键。
工欲善其事必先利其器,我们先来看看对于这样一个多粒子渲染的页面,性能都消耗在了什么地方。
Canvas 卡顿原因
我们常说一个动画很卡,也就是说这个动画的帧率较低。流畅动画的标准一般是 60 FPS。Canvas 渲染动画的基本原理,本质就是不断地重绘画布。
把动画的一帧渲染出来,需要经过以下步骤:
- 计算:处理逻辑,计算每个对象的状态,不涉及 DOM 操作(当然也包含对 Canvas 上下文的操作)。
- 渲染:真正把对象绘制出来
- JavaScript 调用 DOM API(包括 Canvas API)以进行渲染。
- 浏览器(通常是另一个渲染线程)把渲染后的结果呈现在屏幕上的过程。
计算卡顿
由 Javascript 计算过程产生的卡顿,一般是一次性发生的。包括了业务逻辑、坐标计算、对象状态等等。
渲染卡顿
一般称之为掉帧,它是周期性发生的。渲染过程本质上也有两个过程。
- Javascript 调用 DOM API 及 Canvas API 进行渲染。
- GPU 渲染进程把渲染后的结果呈现在屏幕上的过程
所以要优化渲染造成的卡顿,总体思路很简单,归纳为以下几点(Canvas 最佳实践(性能篇)):
- 在每一帧中,尽可能减少调用渲染相关 API 的次数(通常是以计算的复杂化为代价的)。
- 在每一帧中,尽可能调用那些渲染开销较低的 API。
- 在每一帧中,尽可能以「导致渲染开销较低」的方式调用渲染相关 API
解决计算卡顿
计算的卡顿一般出现在某一帧突然需要绘制非常多屏幕内不存在的动画内容,这时在这一帧中造成过大计算量,导致整一帧的时间超过超过16.67ms,阻塞了后续 requestAnimationFrame 的执行,这就会造成一次性的一次卡顿。
另一种情况是每一帧的计算量都稍稍偏多,导致了每一帧的时间都小幅度超过了 16.67ms,但是总体不会造成特别大的一次性卡顿。
针对上面两种情况,也就是说,我们需要解决两种阻塞:
- 较大的阻塞。其原因主要某一帧,也就是动画状态改变的地方,可能是运行复杂算法、大规模的 DOM 操作引起
- 频繁的小阻塞。其原因主要是过高的渲染性能开销,在每一帧中做的事情太多
现在主流的解决计算卡顿的方法有两个。
- 使用 Web Worker,在另一个线程里进行计算
- 将任务拆分为多个较小的任务,插在后续多帧中进行
对于我这个项目,瞬时绘制百万级别的粒子,粒子绘制不存在一些算法和 DOM 操作,对于优化计算卡顿的需求迫切度不高,主要需要进行渲染优化。
解决渲染优化
和真正的绘制相比,计算所产生的开销是其实微不足道的。
减少 Canvas API 调用次数
Canvas API 都在其上下文对象 context 上调用。而每次调用 context 相关的 API,都是对性能的一次消耗。
改变 context 的状态,几乎都与最终的渲染操作有关。譬如我们需要在画布的(100px, 100px)处绘制一个1px半径的粒子:
var context = canvasElement.getContext('2d');
context .fillStyle = rgba(35, 117, 204, .8);
context .beginPath();
context .arc(100, 100, 1, 0, 2 * Math.PI, true);
context .fill();
当我们对 context.fillStyle 赋值,浏览器会需要立刻地做一些事情,这样当我们下次调用 context .fill() 时,保证填充进去的颜色是我们设定的。
------------ 未完成。。。
后续呢。。
后续呢?
这是来自QQ邮箱的假期自动回复邮件。你好,邮件已收到,尽快给你回复。