最近在作canvas粒子动画效果的研究,发现当粒子数量达到必定等级的时候,动画效果会变慢变卡。搜索了一下解决办法,发现离屏渲染是推荐最多的解决办法,那本文就利用离屏渲染实现一个动画效果来对比性能的提高。javascript
查阅了一下资料,概述一下离屏渲染的概念,至关于在屏幕渲染的时候开辟一个缓冲区,将当前须要加载的动画事先在缓冲区渲染完成以后,再显示到屏幕上。html
非离屏渲染就是不创建缓冲区,直接在屏幕上逐个进行绘制,须要重复利用canvas的api。当粒子数量到达必定等级时,性能上会受到较大影响。前端
先建立一个雪花粒子的类,构造相关的属性,定义一个名为snowArray
的数组,将每一个粒子都存入该数组中。count
为雪花的数量。java
class DrawSnow {
constructor(count) {
this.canvas = document.getElementById('canvas');
this.content = this.canvas.getContext('2d')
this.width = this.canvas.width = 1200;
this.height = this.canvas.height = 1000;
this.r = 2.5;
this.timer = null;
this.snowArray= [];
this.count = count;
this.useOffCanvas = false; // 是否使用离屏渲染
this.init()
}
}
复制代码
init()
函数初始化雪花粒子,根据粒子的数量,重复渲染生成随机的位置,并存入数组中。初始化完成以后,开始绘制粒子。并执行动画函数animate()
。chrome
init() {
let OffScreen = '';
if (this.useOffCanvas) {
OffScreen = new OffScreen();
}
for (let i = 0; i < this.count; i++) {
let x = this.width * Math.random();
let y = this.height * Math.random();
this.snowArray.push({
x: x,
y: y
});
this.draw(x, y);
}
this.animate();
}
复制代码
animate()
函数实现了动画的循环,在一次动画执行完成以后,经过window.requestAnimationFrame
来实现重复效果。根据存储在snowArray[]
中的粒子信息,反复进行绘制。canvas
animate() {
this.content.clearRect(0, 0, this.width, this.height);
for (let i in this.snowArray) {
let snow = this.snowArray[i];
snow.y += 2;
if (snow.y >= this.height + 10) {
snow.y = Math.random() * 50;
}
this.draw(snow.x, snow.y);
}
this.timer = requestAnimationFrame(() => {
this.animate();
});
}
复制代码
完成以上的步骤以后,来看一下在浏览器中的效果api
小雪数组
中雪浏览器
大雪dom
上述动图中,右上角为chrome自带的性能分析工具,点击开发者工具performance面板,按快捷键cmd + shift + p
而后输入show rendering
(打开实时查看帧率的面板),能够看到实时的帧率变化。
performance面板的使用在以前有介绍,指路:十分钟上手chrome性能分析面板。
小雪、中雪、大雪须要绘制的粒子分别是80、200、7000个粒子,当粒子数量较少时,动画效果比较顺畅,维持在60FPS左右,当数量增长到7000个时,动画开始卡顿,帧数快速降低。由于录屏工具对实际帧数会产生影响,上述动图可做为参考,实际帧数参考下图:
建立缓冲区,须要额外建立一个canvas画布,将缓冲的画面如今该canvas上绘制好,在经过drawImage()的方式将该画布渲染到屏幕显示的画布上。
首先实现离屏渲染的粒子构造方法,构造完成以后进行绘制,move()
将绘制好的画布经过drawImage
方法在屏幕上展现。
// 粒子类
class OffScreen {
constructor() {
this.canvas = document.createElement('canvas');
this.r = 2.5;
this.width = this.canvas.width = 5;
this.height = this.canvas.height = 5;
this.ctx = this.canvas.getContext('2d');
this.x = this.width * Math.random();
this.y = this.height * Math.random();
this.create();
}
// 建立粒子
create() {
this.ctx.save();
this.ctx.fillStyle = 'rgba(255,255,255)';
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, 2.5, 0, 2 * Math.PI, false);
this.ctx.closePath();
this.ctx.fill();
this.ctx.restore();
}
// 绘制粒子
move(ctx, x, y) {
ctx.drawImage(this.canvas, x, y);
}
}
复制代码
初始化粒子时,判断是不是离屏渲染模式,离屏模式下构造一个离屏粒子,先在画布中画出,当遍历粒子数组时,经过animate()
中执行OffScreen
类中的move()
方法,将粒子展现出来,相似复制黏贴的操做。
class DrawSnow {
constructor(count,useOffCanvas) {
......
this.useOffCanvas = useOffCanvas; // 是否使用离屏渲染
this.init();
}
init() {
let offScreen = '';
if (this.useOffCanvas) {
offScreen = new OffScreen();
}
for (let i = 0; i < this.count; i++) {
let x = this.width * Math.random();
let y = this.height * Math.random();
if (this.useOffCanvas) {
this.snowArray.push({
instance: offScreen,
x: x,
y: y
});
} else {
this.snowArray.push({
x: x,
y: y
});
this.draw(x, y);
}
}
this.animate();
}
animate() {
this.content.clearRect(0, 0, this.width, this.height);
for (let i in this.snowArray) {
let snow = this.snowArray[i];
snow.y += 2;
if (snow.y >= this.height + 10) {
snow.y = Math.random() * 50;
}
if (this.useOffCanvas) {
snow.instance.move(this.content, snow.x, snow.y);
} else {
this.draw(snow.x, snow.y);
}
}
this.timer = requestAnimationFrame(() => {
this.animate();
});
}
}
复制代码
小雪
中雪
大雪
和非离屏渲染进行对比,发现当粒子数量很少时,差距并不明显,当粒子数量达到7000时,有了明显差距。
在上述动图中,离屏渲染下,大雪动画的帧率达到平均23FPS,录屏工具会对性能产生影响,实际的性能以下图:
相比非离屏模式下帧率提高了一倍。
上述例子中,使用离屏渲染确实提高了动画运行的帧率,但不是任什么时候候都适用离屏渲染。
基于上面这个例子,衍生实现另外一个效果,即改变雪花粒子的样式,随机选择粒子的大小位置和透明度,使画面更有层次感。
先观察一下两种模式下的实现效果以及帧率,离屏渲染的帧率反而更低,与以前的结果彻底相反。
新的粒子是随机生成大小位置和透明度,若是经过以前的方式去构建离屏粒子,那么每一个粒子的属性都将相同,没法实现随机效果。在本例中,须要经过循环,将不一样的参数传递给构造函数,至关于屡次调用了构造函数的canvas api。与非离屏渲染模式相比,还增长了建立缓冲区,从缓冲区绘制到屏幕上的性能消耗,因此帧率相比非离屏模式,反而更低。
而在以前的例子中,粒子的大小、颜色、透明度都相同,不须要重复构造,因此只调用了一次构造函数,也只调用了一次绘制的canvas api。
观察下方代码,结合上文中在离屏模式下的构造方式,能够发现,本例中循环构造了新的粒子,也就不断调用了api,并无下降性能的消耗。
init() {
let offScreen = '';
for (let i = 0; i < this.count; i++) {
let x = this.width*Math.random();
let y = this.height*Math.random();
let alpha = (Math.floor(Math.random() * 10) + 1) / 10 / 2;
let color = "rgba(255,255,255," + alpha + ")";
let r = Math.random() * 2 + 1;
if (this.useOffCanvas) {
// 循环构造新的粒子
offScreen = new OffScreen();
this.snowArray.push({
instance: offScreen,
x: x,
y: y,
color: color,
r:r
});
} else {
this.snowArray.push({
x: x,
y: y,
color: color,
r:r
});
this.draw(x,y,color,r);
}
}
this.animate();
}
复制代码
FPS是图像领域中的定义,是指画面每秒传输帧数,通俗来说就是指动画或视频的画面数。
理论上说,FPS 越高,动画会越流畅,目前大多数设备的屏幕刷新率为 60 次/秒,因此一般来说 FPS 为 60 frame/s 时动画效果最好。
当不一样的帧率下,动画的视觉效果如何呢?
因此流畅的动画,帧率须要达到30fps往上。
具体分析能够参考: 【前端性能】Web 动画帧率(FPS)计算
离屏渲染在动画优化上很是多人推荐,但也不是任何状况下均可以利用,离屏渲染首先须要构造一个缓冲区,再将缓冲区中的画面展现到显示屏上,这两个过程也须要消耗性能。
例如上文中第二个例子,并无减小对api的调用,反而离屏的过程增长了性能的消耗,这种状况就不合适采用这种方式。离屏渲染也须要选择合理的使用场景。