一、正常动画实践
为了使用户达到更好的体验,做动画的时候都知道用requestAnimationFrame了,但是他也是有极限的,当绘制的东西足够多或者复杂的时候,频繁的删除与重绘降低了很多性能。
在canvas中粒子系统应该算是比较常见的一种了,现在创建一个canvas画布,并绘制100个粒子在整个画布上由上至下做匀速往返直线运动。
1.创建一个场景类,并初始化基本数据
class Scene { constructor(canvas) { this.canvas = canvas; this.width = this.canvas.width = 800; this.height = this.canvas.height = 500; this.ctx = this.canvas.getContext('2d'); this.amount = 100; // 粒子总数量 this.radius = 5; // 粒子半径 this.particles = []; // 粒子集合 this.speed = 10; // 粒子速度 this.init(); } }
2.创建一个方法绘制粒子
/* 绘制一个粒子 * ctx —— canvas上下文 * x —— 圆心x坐标 * y —— 圆心y坐标 * r —— 圆半径 */ drawParticle(ctx, x, y, r) { ctx.fillStyle = 'black'; ctx.beginPath(); ctx.arc(x, y, r, 0, 2 * Math.PI); ctx.closePath(); ctx.fill(); }
3.初始化所有粒子
上面初始化Scene类是调用的this.init()
。
init() { this.particles = []; // 随机位置生成粒子 for (let i = 0; i < this.amount; i++) { let rx = Math.floor(this.radius + Math.random() * (this.width - this.radius * 2)); let ry = Math.floor(this.radius + Math.random() * (this.height - this.radius * 2)); this.particles.push({ x: rx, y: ry, isMax: false // 是否达到边界 }); this.drawParticle(this.ctx, rx, ry, this.radius); } // 动画 this.animate(); }
4.运动动画
animate() { this.ctx.clearRect(0, 0, this.width, this.height); for (let i = 0; i < this.particles.length; i++) { let particle = this.particles[i]; // 判断是是否到达边界 if (particle.isMax) { particle.y -= this.speed; if (particle.y <= 0 + this.radius) { particle.isMax = false; particle.y += this.speed; } } else { particle.y += this.speed; if (particle.y >= this.height - this.radius) { particle.isMax = true; particle.y -= this.speed; } } // 重绘 this.drawParticle(this.ctx, particle.x, particle.y, this.radius); } let self = this; requestAnimationFrame(() => { self.animate(); }); }
5.分析
从上面最终展示的效果来看100个还是很流畅的,我试着增加数量到1000,发现依然没问题,fps很稳定在60左右,如下图:
1000个粒子
接着增加数量到4000,发现fps已经开始变化了,稳定在50左右,数量越多越明显,如下图。
4000个粒子
二、离屏渲染
1.为什么使用离屏渲染 && 离屏渲染是什么
正如上文所说,当粒子量级达到一定数量的时候,性能开始降低,帧率开始下降,这是我们不想看到的,因为这很影响用户体验。
离屏渲染到底是什么?在Mozilla文档上有简单介绍,大概意思就是说再创建一个新的canvas,然后将要绘制的图形,先在新的canvas中绘制,然后使用drawImage()将新的canvas画到当前的canvas上。网上看了一些也没有讲的特别清楚,也动手实践了一下。
2.创建粒子(圆形)类
class Particle { constructor(r) { this.canvas = document.createElement('canvas'); // 创建一个新的canvas this.width = this.canvas.width = r * 2; // 创建一个正好包裹住一个粒子canvas this.height = this.canvas.height = r * 2; this.ctx = this.canvas.getContext('2d'); this.x = this.width / 2; this.y = this.height / 2; this.r = r; // 半径 this.create(); } // 创建粒子 create() { this.ctx.save(); this.ctx.fillStyle = 'black'; this.ctx.beginPath(); this.ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI); this.ctx.closePath(); this.ctx.fill(); this.ctx.restore(); } // 移动粒子 move(ctx, x, y) { // 将这个新创建的canvas画到真正显示的canvas上 ctx.drawImage(this.canvas, x, y); } }
3.初始化小球以及动画代码
// init方法中的for循环 for (let i = 0; i < this.amount; i++) { let rx = Math.floor(this.radius + Math.random() * (this.width - this.radius * 2)); let ry = Math.floor(this.radius + Math.random() * (this.height - this.radius * 2)); // 每一个粒子创建一个实例 let particle = new Particle(this.radius); this.particles.push({ instance: particle, x: rx, y: ry, isMax: false }); }
将animate()
方法中原本调用drawParticle()
的地方改为:
// this.drawParticle(this.ctx, particle.x, particle.y, this.radius); // ↓↓ particle.instance.move(this.ctx, particle.x, particle.y);
4.分析
通过上面的改造,效果如下图:
2000个粒子
但是,发现在2000个粒子的时候fps已经降低到25左右,上面没有使用离屏渲染的时候4000个粒子fps还有50,由此感觉离屏渲染反而降低了性能。
三、离屏渲染——代码优化
1.优化思路
在上面相当于创建了跟粒子个数相同的离屏canvas,而且每个都是一样的,既然都是一样的,那干脆就只new一个粒子的实例出来,这样就只创建了一个离屏的canvas,然后通过在不同位置多次绘制这个离屏canvas,实现动画效果。
2.代码修改
修改init
方法,将循环中创建实例的方法放到循环外面。
// 只创建一个实例 let particle = new Particle(this.radius); for (let i = 0; i < this.amount; i++) { let rx = Math.floor(this.radius + Math.random() * (this.width - this.radius * 2)); let ry = Math.floor(this.radius + Math.random() * (this.height - this.radius * 2)); this.particles.push({ instance: particle, x: rx, y: ry, isMax: false }); }
3.分析
优化了代码之后,果然达到了预期的效果,下面是4000个粒子的fps如下图:
四、写在后面
通过上面的操作,发现离屏渲染虽然可以优化动画的性能,但是从上面可以看出频繁的创建和销毁大量canvas也会很影响性能的,所以这中间要有一个取舍。另外,凡事都有一个限度,离屏渲染也不是万能的,有兴趣的可以试试,在这个例子中,如果粒子数量达到7000、8000或者9000乃至更多其实还是有很明显的卡顿。
当然,canvas的一些api也是消耗性能的,所以最后发现,要做好性能优化,首先代码肯定是要优化,另外就是使用像离屏渲染之类的方法。
转自:https://blog.csdn.net/qq_26733915/article/details/81675124
回过头来一想,都说离屏渲染提升性能,难道是操作有问题,接着又进行了一波优化。4000个粒子