Canvas烟花动画核心是粒子系统:每朵烟花由爆发点和多个带位置、速度、颜色、透明度、半径及生命周期的粒子组成,通过逐帧update更新状态并draw渲染,点击触发并在requestAnimationFrame中循环执行。

网页端用 Canvas 制作烟花动画,核心是模拟粒子运动 + 逐帧渲染 + 合理控制生命周期。不依赖框架,纯 JS 就能实现视觉效果强烈的绽放效果。
一、搭建基础 Canvas 环境
先在 HTML 中定义 canvas 元素,并获取 2D 上下文:
JS 中初始化:
const canvas = document.getElementById('fireworks');
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height); // 清屏背景(可设透明)
二、设计烟花粒子系统
每朵烟花由一个“爆发点”和数十至上百个“粒子”组成。每个粒子需记录:
- 当前位置(x, y)
- 速度向量(vx, vy),初始由随机角度和初速度决定
- 颜色(可用 HSL 动态生成渐变色,如 hsl(10 + Math.random()*50, 100%, 60%))
- 透明度(alpha)和衰减率(随时间降低,实现淡出)
- 半径(radius)及缩小速率(模拟消散)
- 存活帧数(lifetime),归零即销毁
推荐用类封装粒子:
class Particle {
constructor(x, y) {
this.x = x;
this.y = y;
const angle = Math.random() * Math.PI * 2;
const speed = 2 + Math.random() * 4;
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
this.alpha = 1;
this.decay = 0.97 + Math.random() * 0.02;
this.radius = 1 + Math.random() * 2;
this.lifetime = 60 + Math.floor(Math.random() * 40);
}
update() {
this.x += this.vx;
this.y += this.vy;
this.vy += 0.05; // 加入轻微重力 this.alpha *= this.decay;
this.radius *= 0.98;
this.lifetime--;
}
draw(ctx) {
ctx.save();
ctx.globalAlpha = this.alpha;
ctx.fillStyle = `hsl(${30 + this.lifetime}, 100%, 60%)`;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
三、触发与管理烟花实例
点击或定时生成烟花:
- 监听 click 事件,在鼠标位置创建新烟花(即生成一批粒子)
- 维护一个 particles 数组,每帧遍历更新并绘制
- 每次 update 后过滤掉 lifetime ≤ 0 的粒子:particles = particles.filter(p => p.lifetime > 0)
- 为避免性能爆炸,可限制最大粒子数(如不超过 5000 个)
简单触发示例:
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
for (let i = 0; i particles.push(new Particle(x, y));
}
});
四、优化视觉与性能
让效果更炫酷的关键细节:
- 拖尾效果:不完全清屏,用半透明黑色矩形轻度覆盖上一帧(ctx.fillStyle = 'rgba(0,0,0,0.1)'; ctx.fillRect(0,0,w,h))
- 多色混合:给同一朵烟花的粒子分配相近但有差异的 hue 值,形成光晕感
-
声音配合:用
AudioContext播放短促爆破音效(可选) -
缩放适配:监听
window.resize,动态设置 canvas.width/height 并重绘 - requestAnimationFrame 驱动主循环,比 setInterval 更顺滑
function animate() {
ctx.fillStyle = 'rgba(0,0,0,0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
particles.forEach(p => { p.update(); p.draw(ctx); });
requestAnimationFrame(animate);
}
animate();


















