導語:在平常的開發過程當中,咱們會經常會用到
canvas
來製做一些動畫特效,其中有一個動畫種類,須要咱們生成必定數量,形狀相似且行爲基本一致的粒子,經過這些粒子的運動,來展示動畫效果,好比:下雨
,閃爍的星空
。。。此類效果統一可稱爲粒子系統動畫。
簡單地說,粒子系統是一些粒子的集合,經過指定發射源 (即每一個粒子的起始位置) 發射粒子流 (即粒子的動畫效果)。javascript
canvas粒子動畫系統解決方案
開始搭建一個粒子系統java
粒子系統源碼使用說明es6
總結github
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<canvas id="example"></canvas>
</body>
<script> var cvs = document.getElementById('example'); var ctx = cvs.getContext('2d'); var width = 400; var height = 400; cvs.width = 400; cvs.height = 400; var particle = []; var lineAnimation; function createItem(amount) { for (let i = 0; i < amount; i++) { particle.push({ posX: Math.round(Math.random() * width), posY: Math.round(Math.random() * height), r: 4, color: Math.random() < 0.5 ? '#d63e3e' : '#23409b' }); } draw(); }; function draw() { ctx.clearRect(0, 0, width, height); particle.map((item, index) => { ctx.beginPath(); ctx.arc(item.posX, item.posY, item.r, 0, 2 * Math.PI); ctx.fillStyle = item.color; ctx.fill(); //畫實心圓 ctx.closePath(); item.posY = item.posY + 2; if (item.posY > height) { item.posX = Math.round(Math.random() * width); item.posY = Math.round(Math.random() * height); }; }) lineAnimation = requestAnimationFrame(draw); } function stop() { cancelAnimationFrame(lineAnimation); } createItem(100); </script>
</html>
複製代碼
canvas
畫布。既然粒子系統有這麼多的通用性, 爲何咱們不能把其中通用的地方抽離出來,創建一個粒子系統呢?chrome
根據上一部分總結出的共性,咱們能夠寫出一個粒子系統的大概組成代碼:canvas
const STATUS_RUN = 'run';
const STATUS_STOP = 'stop';
//粒子系統基類
class Particle {
//1. 建立 `canvas` 畫布
constructor(idName, width, height, options) {
this.canvas = document.getElementById(`${idName}`);
this.ctx = this.canvas.getContext('2d'); //canvas執行上下文
this.timer = null; //動畫運行定時器,採用requestAnimationFrame
this.status = STATUS_STOP; //動畫執行狀態 默認爲stop
this.options = options || {}; //配置(粒子數量,速度等)
this.canvas.width = width;
this.canvas.height = height;
this.width = width;
this.height = height;
this.init();
};
//2. 初始化粒子
init() {
};
//3. 繪製粒子到畫布
draw() {
let self = this;
let { ctx, width, height } = this;
ctx.clearRect(0, 0, width, height);
this.moveFunc(ctx, width, height);
this.timer = requestAnimationFrame(() => {
self.draw();
});
};
//4. 定義粒子的運動方式
moveFunc() {
};
//5. 控制動畫的播放與暫停。
run() {
if (this.status !== STATUS_RUN) {
this.status = STATUS_RUN;
this.draw();
}
};
stop() {
this.status = STATUS_STOP;
cancelAnimationFrame(this.timer);
};
//6. 清除畫布
clear() {
this.stop();
this.ctx.clearRect(0, 0, this.width, this.height);
};
};
export {
Particle
}
複製代碼
咱們經過這個方法改寫下最開始的例子:dom
import { Particle } from "../lib/particleI.js";
class exampleMove extends Particle {
//2. 初始化粒子
init() {
this.particle = [];
let amount = this.options.amount;
let { width, height } = this;
for (let i = 0; i < amount; i++) {
this.particle.push({
posX: Math.round(Math.random() * width),
posY: Math.round(Math.random() * height),
r: 4,
color: Math.random() < 0.5 ? '#d63e3e' : '#23409b'
});
}
};
//4. 定義粒子的運動方式
moveFunc(ctx, width, height) {
this.particle.map(item => {
item.posY = item.posY + 2;
if (item.posY > height) {
item.posX = Math.round(Math.random() * width);
item.posY = Math.round(Math.random() * height);
};
this.createParticle(ctx, item.posX, item.posY, item.r, item.color);
});
};
//粒子形狀
createParticle(ctx, x, y, r, color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
};
//4. 定義粒子的運動方式
moveFunc(ctx, width, height) {
this.particle.map(item => {
item.posY = item.posY + 2;
if (item.posY > height) {
item.posX = Math.round(Math.random() * width);
item.posY = Math.round(Math.random() * height);
};
this.createParticle(ctx, item.posX, item.posY, item.r, item.color);
});
};
}
複製代碼
新建實例,讓粒子系統運動:工具
var example = new exampleMove('example', 400, 400, { speed: 3, amount: 8 });
example.run();
複製代碼
寫到這裏, 一個小小的粒子系統就搭建完成了,咱們看下總結看下:
關於粒子系統的這些共性:
canvas
畫布。 (基類完成)因爲每一個人的粒子動畫的展示方式有所不一樣,因此二、4
兩點須要,本身繼承進行修改。
文章到此你覺得就完了嘛?
咱們把剛纔搭建的粒子系統的數量提升到 6000
個看一下:
60
,不然動畫會出現卡頓感!!!ps:關於性能分析,能夠看我以前的一篇總結:兄dei,據說你動畫很卡?
在開始以前,咱們是否是要分析一下,爲何咱們的粒子動畫到達必定數量之後會卡!!
根據chrome
性能分析工具,觀察下圖:
不難看出每一幀大部分的時間消耗都在canvas Api
的調用中。
看似一個 ctx.fillStyle = '#f00'
整的跟 var a = '#f00'
差很少似的,實際的消耗是遠遠大約簡單的變量賦值的,以下代碼:
var cvs = document.getElementById('example');
var ctx = cvs.getContext('2d');
var timeStart = (new Date()).getTime();
var count;
for (var i = 0; i < Math.pow(10, 7); i++) {
// ctx.fillStyle = '#f00';
count = '#f00';
};
var timeEnd = (new Date()).getTime();
console.log('during:::', timeEnd - timeStart);
複製代碼
渲染相關 API 的次數
,那麼該如何避開呢?咱們爲每一個粒子單首創建一個
canvas
畫布,把粒子先在畫布中畫出。
// 離屏粒子類(這裏的畫布大小盡可能和粒子大小保持一致,畫布太大也會消耗性能);
class offScreenItem {
constructor(width, height, create) {
this.canvas = document.createElement('canvas');
this.width = this.canvas.width = width * 2;
this.height = this.canvas.height = height * 2;
this.ctx = this.canvas.getContext('2d');
//在畫布上繪製粒子
create(this.ctx, this.width, this.height);
};
// 移動粒子(使用 drawImage 方法,經過改變粒子canvas畫布的位置,達到運動的效果)
move(ctx, x, y) {
if (this.canvas.width && this.canvas.height) {
ctx.drawImage(this.canvas, x, y);
}
}
}
複製代碼
6000
個粒子,可是幀率已經幾乎回到了60
, 開森!!!。注意:
在建立離屏粒子實例時,必定要按種類建立,好比,上圖中,實際上我只有紅藍兩種圓,因此只要實例化兩次就好,千萬不要每個粒子都實例化一次,會十分消耗內存,還不如沒開啓離屏渲染的時候。
import { Particle, offScreenItem } from "../lib/particle.js";
class exampleMove extends Particle {
//粒子形狀繪製
createParticle(ctx, x, y, r, color) {
//todo...
};
//粒子如何運動
moveFunc(ctx, width, height) {
//todo...
};
//離屏粒子初始化位置
createOffScreenInstance(width, height, amount) {
//todo...
};
//正常粒子初始化位置
createNormalInstance(width, height, amount) {
//todo...
}
}
/** * @param {[String]} id [canvas畫布的id] * @param {[Number]} width [canvas畫布的寬] * @param {[Number]} height [canvas畫布的高] * @param {[Object]} option [粒子系統的配置{speed: 3, amount: 800}] * @param {[Boolean]} offScreen [是否採用離屏渲染] * */
var example = new exampleMove(id, width, height, option, offScreen);
//運動
example.run();
//中止
example.stop();
//清理畫布
example.clear();
複製代碼
1. 什麼是粒子系統?
2. 爲何咱們須要寫一個粒子系統?
3. 當粒子數量到達必定的瓶頸,咱們應該如何優化?
s
canvas
自己有不少能夠優化的點,性能問題,不是可以單單的靠一兩個通用的解決方案就所有解決的,本文只是其中一個方向,但願能夠給你們帶來一些啓發和思考。