最近在作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的調用,反而離屏的過程增長了性能的消耗,這種狀況就不合適採用這種方式。離屏渲染也須要選擇合理的使用場景。