代码效果图:

属性设计

烟花状态:烟花应有三个状态:

* 升空
* 等待炸裂
* 炸裂后
烟花:发射点(x, y),爆炸点(xEnd, yEnd),升空后等待炸裂时间(wait),炸裂后微粒个数(count),烟花半径(radius)

烟花炸裂后微粒:自身位置(x, y),自身大小(size),自身速度(rate),最大烟花半径(radius)。

config:为全局变量,以及控制参数,包括画布宽高,设定烟花属性等。

设定全局变量
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();
完善 

此时烟花是这样的,感觉少了点小尾巴。

现在我们每一帧都是清除了画布,如果要加上小尾巴其实也很简单,每一帧都不要清除画布,而是覆盖一层新的有透明度的天空上去。
//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);

 

这时就变成这样了。

但是,还是缺少了在爆炸瞬间 天空变亮的场景。

那么在画烟花的时候,先会获取一下烟花的颜色以及透明度。
// *****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);

 到现在就算是大功告成了。

结束语 
这份完整版的代码已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码【免费获取】。

 

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信