一、正常動畫實踐
為了使用戶達到更好的體驗,做動畫的時候都知道用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個粒子
