多代碼,慎讀!!!javascript
完整項目預覽----預覽地址;java
煙花狀態:煙花應有三個狀態:git
煙花:發射點(x, y),爆炸點(xEnd, yEnd),升空後等待炸裂時間(wait),炸裂後微粒個數(count),煙花半徑(radius)github
煙花炸裂後微粒:自身位置(x, y),自身大小(size),自身速度(rate),最大煙花半徑(radius)。canvas
config:爲全局變量,以及控制參數,包括畫布寬高,設定煙花屬性等。dom
const config = { width: 360, height: 600, canvases: ['bg', 'firework'], skyColor: '210, 60%, 5%, 0.2)', fireworkTime:{min:30,max:60}, //煙花參數自己有默認值 傳入undefined則使用默認參數 fireworkOpt:{ x: undefined, y: undefined, xEnd: undefined, yEnd: undefined, count: 300, //炸裂後粒子數 wait: undefined, //消失後 => 炸裂 等待時間 } }
class Particle{ //默認值寫法 constructor({x, y, size = 1, radius = 1.2} = {}){ this.x = x; this.y = y; this.size = size; this.rate = Math.random(); //每一個微粒移動的速度都是隨機不一樣的 this.angle = Math.PI * 2 * Math.random(); //每一個微粒的偏移角度 //每次微粒移動速度分解爲橫縱座標的移動。 this.vx = radius * Math.cos(this.angle) * this.rate; this.vy = radius * Math.sin(this.angle) * this.rate; } go(){ this.x += this.vx; this.y += this.vy; this.vy += 0.02; //重力影響 y越大實際越偏下 //空氣阻力 this.vx *= 0.98; this.vy *= 0.98; } //畫出微粒的位置 render(ctx){ this.go(); ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2, false); ctx.fill(); } }
class Firework{ constructor({x, y = config.height, xEnd, yEnd, count = 300, wait} = {}){ //煙花自身屬性 this.x = x || config.width / 8 + Math.random() * config.width * 3 / 4; this.y = y; this.xEnd = xEnd || this.x; this.yEnd = yEnd || config.width / 8 + Math.random() * config.width * 3 / 8; this.size = 2; this.velocity = -3; this.opacity = 0.8; this.color = `hsla(${360 * Math.random() | 0},80%,60%,1)`; this.wait = wait || 30 + Math.random() * 30; //微粒個數等 this.count = count; this.particles = []; this.createParticles(); this.status = 1; } //建立微粒 createParticles(){ for(let i = 0;i < this.count; ++i){ this.particles.push(new Particle({x:this.xEnd, y:this.yEnd})); } } //升空 rise(){ this.y += this.velocity * 1; this.velocity += 0.005; //升空時產生的阻力 //煙花升空到目標位置開始漸隱 if(this.y - this.yEnd <= 50){ this.opacity = (this.y - this.yEnd) / 50; } //若是到了目標位置 就開始第二個狀態 if(this.y <= this.yEnd){ this.status = 2; } } //渲染煙花 煙花全部動做完成以後返回false render(ctx){ switch(this.status){ case 1: //升空 ctx.save(); ctx.beginPath(); ctx.globalCompositeOperation = 'lighter'; ctx.globalAlpha = this.opacity; ctx.translate(this.x, this.y); ctx.scale(0.8, 2.3); ctx.translate(-this.x, -this.y); ctx.fillStyle = this.color; ctx.arc(this.x + Math.sin(Math.PI * 2 * Math.random()) / 1.2, this.y, this.size, 0, Math.PI * 2, false); ctx.fill(); ctx.restore(); this.rise(); return true; break; case 2: //煙花消失階段,等待炸裂 if(--this.wait <= 0){ this.opacity = 1; this.status = 3; } return true; break; case 3: //炸裂以後 渲染煙花微粒 ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.globalAlpha = this.opacity; ctx.fillStyle = this.color; for(let i = 0;i < this.particles.length;++i){ this.particles[i].render(ctx); } ctx.restore(); this.opacity -= 0.01; return this.opacity > 0; break; default: return false; } } }
const canvas = { init: function(){ //一些屬性的設定 能夠不用管 this.setProperty(); this.renderBg(); //循環體 **主要 this.loop(); }, setProperty: function(){ this.fireworks = []; this.width = config.width; this.height = config.height; this.fireworkTime = (config.fireworkTime.min + (config.fireworkTime.max - config.fireworkTime.min) * Math.random()) | 0; this.bgCtx = document.querySelector('#bg').getContext('2d'); this.fireworkCtx = document.querySelector('#firework').getContext('2d'); }, renderBg(){ this.bgCtx.fillStyle = 'hsla(210, 60%, 5%, 0.9)' this.bgCtx.fillRect(0, 0, this.width, this.height); }, loop(){ requestAnimationFrame(this.loop.bind(this)); this.fireworkCtx.clearRect(0, 0, this.width, this.height); //隨機建立煙花 if(--this.fireworkTime <= 0){ this.fireworks.push(new Firework(config.fireworkOpt)); //每次到點以後從新設置煙花產生時間 (|0轉化爲整數) this.fireworkTime = (config.fireworkTime.min + (config.fireworkTime.max - config.fireworkTime.min) * Math.random()) | 0; } for(let i = this.fireworks.length - 1; i >= 0; --i){ //渲染煙花 (若返回值爲false則移除煙花) !this.fireworks[i].render(this.fireworkCtx) && this.fireworks.splice(i,1); } } } canvas.init();
此時煙花是這樣的,感受少了點小尾巴。ide
如今咱們每一幀都是清除了畫布,若是要加上小尾巴其實也很簡單,每一幀都不要清除畫布,而是覆蓋一層新的有透明度的天空上去。oop
//canvas.loop方法 // this.fireworkCtx.clearRect(0, 0, this.width, this.height); this.fireworkCtx.fillStyle = config.skyColor; this.fireworkCtx.fillRect(0,0,this.width,this.height);
這時就變成這樣了。ui
可是,仍是缺乏了在爆炸瞬間 天空變亮的場景。this
那麼在畫煙花的時候,先會獲取一下煙花的顏色以及透明度。
// *****Firework constructor // this.color = `hsla(${360 * Math.random() | 0},80%,60%,1)`; this.hue = 360 * Math.random() | 0; this.color = `hsla(${this.hue},80%,60%,1)`;
// *****Firework 新增實例方法 getSkyColor(){ const skyColor = { //只有炸裂階段才返回亮度 lightness: this.status == 3 ? this.opacity : 0 , hue: this.hue }; return skyColor; }
// *****config 修改config的skyColor // skyColor: 'hsla(210, 60%, 5%, 0.2)', skyColor: 'hsla({hue}, 60%, {lightness}%, 0.2)',
// canvas.loop方法 //this.fireworkCtx.fillStyle = config.skyColor; //每次替換色調與亮度值。 this.fireworkCtx.fillStyle = config.skyColor.replace('{lightness}', 5 + this.skyColor.lightness * 15).replace('{hue}' , this.skyColor.hue); this.skyColor = { //新增 lightness: 0, hue: 210 }; for(let i = this.fireworks.length - 1; i >= 0; --i){ //新增 天空顏色爲最亮的煙花的顏色 this.skyColor = this.skyColor.lightness >= this.fireworks[i].getSkyColor().lightness ? this.skyColor : this.fireworks[i].getSkyColor(); !this.fireworks[i].render(this.fireworkCtx) && this.fireworks.splice(i,1); }
到如今就算是大功告成了。
若是以爲還不錯,請star一個吧。
參考了codepen.io上的不少做品。
主要參考 --- fireworks seen in the countryside