1. 项目概述:HTML烟花特效的创意实现
去年春节前夕,我在为个人网站设计节日装饰时,偶然发现Canvas绘制烟花的效果出奇地好。这个用HTML实现多种烟花特效的项目,本质上是通过前端三件套(HTML+CSS+JavaScript)模拟真实烟花的物理运动轨迹。不同于简单的GIF动画,这种方案能实现用户交互式烟花秀——点击屏幕任意位置就会绽放对应颜色的烟花,且每个烟花粒子都遵循抛物线运动规律。
核心实现原理可分为三个层次:基础架构使用HTML5的Canvas元素作为画布;样式控制通过CSS定义烟花颜色库;核心动画逻辑则用JavaScript实现粒子系统和物理引擎。这种技术组合既保证了性能(Canvas硬件加速),又提供了足够的灵活性(JS控制动画细节)。
关键提示:所有示例代码都经过Chrome、Firefox、Edge三大浏览器测试,源码已托管在Gitee(后文附链接),建议边阅读边动手实践。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术实现详解
2.1 基础环境搭建
首先创建标准HTML5文档结构:
html复制<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>交互式烟花秀</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #000;
cursor: crosshair;
}
canvas {
display: block;
}
</style>
</head>
<body>
<canvas id="fireworks"></canvas>
<script src="fireworks.js"></script>
</body>
</html>
这里有几个关键细节:
overflow: hidden防止页面滚动条影响全屏效果background: #000黑色背景模拟夜空cursor: crosshair增强点击发射的仪式感
2.2 Canvas初始化配置
在fireworks.js中初始化画布:
javascript复制const canvas = document.getElementById('fireworks');
const ctx = canvas.getContext('2d');
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
这里使用动态调整画布尺寸的策略,确保在任何设备上都能全屏显示。实测发现,如果在移动端使用,需要额外添加触摸事件支持:
javascript复制canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
launchFirework(e.touches[0].clientX, e.touches[0].clientY);
});
2.3 烟花粒子系统设计
烟花特效的核心是粒子系统,每个烟花由数百个粒子组成:
javascript复制class Particle {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.color = color;
this.velocity = {
x: (Math.random() - 0.5) * 8,
y: (Math.random() - 0.5) * 8
};
this.alpha = 1;
this.decay = Math.random() * 0.015 + 0.01;
}
update() {
this.velocity.y += 0.05; // 重力加速度
this.x += this.velocity.x;
this.y += this.velocity.y;
this.alpha -= this.decay;
}
draw() {
ctx.save();
ctx.globalAlpha = this.alpha;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, 2, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
ctx.restore();
}
}
参数调优经验:
- 重力值0.05模拟真实下落速度
- 随机初速度(-4,4)区间产生自然扩散
- alpha衰减系数0.01-0.025控制粒子存活时间
2.4 发射轨迹算法
烟花的上升阶段采用二次贝塞尔曲线模拟:
javascript复制class Firework {
constructor(x, y, targetX, targetY) {
this.x = x;
this.y = y;
this.targetX = targetX;
this.targetY = targetY;
this.distance = Math.sqrt(Math.pow(targetX - x, 2) + Math.pow(targetY - y, 2));
this.angle = Math.atan2(targetY - y, targetX - x);
this.speed = 2;
this.particles = [];
}
update() {
// 贝塞尔曲线控制点
const cpX = (this.x + this.targetX) / 2;
const cpY = Math.min(this.y, this.targetY) - this.distance * 0.3;
// 计算当前进度
this.t = this.t || 0;
this.t += 0.01;
// 二次贝塞尔曲线公式
this.x = Math.pow(1-this.t, 2) * this.x +
2 * (1-this.t) * this.t * cpX +
Math.pow(this.t, 2) * this.targetX;
this.y = Math.pow(1-this.t, 2) * this.y +
2 * (1-this.t) * this.t * cpY +
Math.pow(this.t, 2) * this.targetY;
// 到达目标点后爆炸
if (this.t >= 1) {
this.explode();
return false;
}
return true;
}
explode() {
const particleCount = 150;
const hue = Math.floor(Math.random() * 360);
for (let i = 0; i < particleCount; i++) {
this.particles.push(new Particle(
this.x,
this.y,
`hsl(${hue}, 100%, 50%)`
));
}
}
}
3. 动画循环与性能优化
3.1 请求动画帧实现
使用requestAnimationFrame实现流畅动画:
javascript复制let fireworks = [];
let particles = [];
function animate() {
// 半透明背景实现拖尾效果
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 更新所有烟花
fireworks = fireworks.filter(firework => {
return firework.update();
});
// 更新所有粒子
particles.forEach((particle, index) => {
particle.update();
particle.draw();
if (particle.alpha <= 0) {
particles.splice(index, 1);
}
});
requestAnimationFrame(animate);
}
animate();
3.2 性能优化技巧
- 对象池技术:复用已销毁的粒子对象
javascript复制const particlePool = [];
function getParticle(x, y, color) {
if (particlePool.length > 0) {
const p = particlePool.pop();
p.x = x;
p.y = y;
p.color = color;
p.alpha = 1;
return p;
}
return new Particle(x, y, color);
}
- 离屏Canvas:对静态元素使用缓存
javascript复制const offscreenCanvas = document.createElement('canvas');
const offscreenCtx = offscreenCanvas.getContext('2d');
// ...预渲染操作...
- 节流控制:限制同时存在的烟花数量
javascript复制const MAX_FIREWORKS = 5;
function launchFirework(x, y) {
if (fireworks.length < MAX_FIREWORKS) {
fireworks.push(new Firework(
canvas.width / 2,
canvas.height,
x,
y
));
}
}
4. 特效扩展方案
4.1 多形状粒子
修改Particle类的draw方法实现不同形状:
javascript复制draw() {
ctx.save();
ctx.globalAlpha = this.alpha;
ctx.fillStyle = this.color;
switch(this.shape) {
case 'circle':
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
break;
case 'star':
drawStar(this.x, this.y, 5, this.size, this.size/2);
ctx.fill();
break;
case 'heart':
drawHeart(this.x, this.y, this.size);
ctx.fill();
break;
}
ctx.restore();
}
function drawStar(x, y, spikes, outerRadius, innerRadius) {
let rot = Math.PI/2*3;
let step = Math.PI/spikes;
ctx.beginPath();
ctx.moveTo(x, y - outerRadius);
for(let i=0; i<spikes; i++){
x = x + Math.cos(rot) * outerRadius;
y = y + Math.sin(rot) * outerRadius;
ctx.lineTo(x, y);
rot += step;
x = x + Math.cos(rot) * innerRadius;
y = y + Math.sin(rot) * innerRadius;
ctx.lineTo(x, y);
rot += step;
}
ctx.lineTo(x, y - outerRadius);
ctx.closePath();
}
4.2 3D透视效果
通过缩放模拟远近景深:
javascript复制update() {
// 原有代码...
// 添加透视效果
this.scale = this.scale || 1;
if (this.y < canvas.height * 0.3) {
this.scale = 0.7; // 远处的烟花变小
} else if (this.y > canvas.height * 0.7) {
this.scale = 1.3; // 近处的烟花变大
}
}
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.scale(this.scale, this.scale);
ctx.translate(-this.x, -this.y);
// 原有绘制代码...
ctx.restore();
}
4.3 音效增强
添加爆炸音效:
javascript复制const audioContext = new (window.AudioContext || window.webkitAudioContext)();
function playExplosionSound() {
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.type = 'sine';
oscillator.frequency.value = 100 + Math.random() * 800;
gainNode.gain.value = 0.1;
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.start();
gainNode.gain.exponentialRampToValueAtTime(
0.001,
audioContext.currentTime + 0.5
);
oscillator.stop(audioContext.currentTime + 0.5);
}
5. 常见问题排查
5.1 粒子闪烁问题
现象:动画过程中出现画面闪烁
解决方案:
javascript复制// 在animate函数开头添加
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 替换原来的半透明背景绘制
5.2 移动端性能差
现象:手机浏览器卡顿明显
优化方案:
- 减少粒子数量:
javascript复制const particleCount = window.innerWidth < 768 ? 80 : 150;
- 使用will-change提示浏览器:
css复制canvas {
will-change: transform;
}
5.3 颜色过于单调
扩展颜色方案:
javascript复制// 使用HSL色彩空间生成渐变色调
const hue = Date.now() % 360;
const color = `hsl(${hue}, 100%, 50%)`;
// 或者在爆炸时生成互补色
const complementaryHue = (hue + 180) % 360;
6. 完整源码结构
最终项目目录结构如下:
code复制/fireworks-demo
│── index.html
│── /js
│ ├── fireworks.js # 主逻辑
│ ├── particles.js # 粒子系统
│ └── utils.js # 工具函数
│── /sounds
│ └── explosion.mp3 # 音效文件
└── README.md
核心函数调用关系:
- index.html 加载基础Canvas
- fireworks.js 初始化动画循环
- particles.js 管理所有粒子对象
- 用户交互触发launchFirework()
我在实际开发中发现,Chrome的性能分析工具非常有用。通过Performance面板记录动画运行情况,可以精确找到耗时操作。典型优化点包括:减少Canvas状态切换、合并绘制调用、避免在动画循环中创建新对象等。
