HTML5 Canvas烟花模拟:从粒子系统到交互式新年贺卡

发布时间:2026/7/3 20:35:26

HTML5 Canvas烟花模拟:从粒子系统到交互式新年贺卡 1. 粒子系统基础从物理规律到代码实现烟花效果的本质是粒子系统的经典应用场景。想象一下当烟花升空爆炸时无数发光粒子向四周飞散每个粒子都遵循简单的物理规则受重力影响下落、速度逐渐衰减、颜色随时间变化。在Canvas中实现这种效果我们需要建立三个核心模型粒子对象每个粒子需要记录当前位置(x,y)、速度(vx,vy)、半径、颜色和生命周期发射器控制粒子的生成规则包括发射角度、初始速度和生成频率环境参数重力加速度、空气阻力系数等物理参数下面是一个基础粒子类的实现class Particle { constructor(x, y, vx, vy, radius, color, life) { this.x x this.y y this.vx vx this.vy vy this.radius radius this.color color this.life life this.age 0 } update() { this.x this.vx this.vy 0.1 // 重力加速度 this.y this.vy this.age this.radius * 0.99 // 尺寸衰减 } draw(ctx) { ctx.beginPath() ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2) ctx.fillStyle this.color ctx.fill() } }这个基础类已经能实现粒子受重力下落的物理效果。我在实际项目中测试发现调整重力系数(0.1)和衰减系数(0.99)会显著影响视觉效果。数值越大烟花下落越快衰减系数越小粒子消失得越快。2. 烟花特效的进阶实现技巧2.1 多阶段动画模拟真实的烟花效果通常包含三个阶段上升阶段从地面发射到空中指定高度爆炸阶段在顶点瞬间产生大量粒子消散阶段爆炸后的粒子逐渐下落消失我们可以用状态机来管理这三个阶段class Firework { constructor(x, y, targetY) { this.state rising // rising/exploding/decaying this.particles [] // 初始化上升阶段的火箭粒子 this.particles.push(new RocketParticle(x, y, targetY)) } update() { if(this.state rising this.particles[0].y this.targetY) { this.explode() this.state exploding } // 更新所有粒子 this.particles.forEach(p p.update()) // 移除生命周期结束的粒子 this.particles this.particles.filter(p p.age p.life) } explode() { // 生成爆炸粒子 for(let i0; i150; i) { const angle Math.random() * Math.PI * 2 const speed Math.random() * 5 2 this.particles.push( new ExplosionParticle( this.particles[0].x, this.particles[0].y, Math.cos(angle) * speed, Math.sin(angle) * speed ) ) } } }2.2 颜色与形状优化普通圆形粒子看起来比较单调我尝试过几种增强视觉效果的方法渐变色粒子使用Canvas的径向渐变替代纯色填充function createGradient(ctx, x, y, r, color) { const gradient ctx.createRadialGradient(x, y, 0, x, y, r) gradient.addColorStop(0, color) gradient.addColorStop(1, rgba(255,255,255,0)) return gradient }形状粒子将文字或图形转化为粒子阵列function textToParticles(text, x, y) { // 创建临时canvas获取文字像素数据 const tempCanvas document.createElement(canvas) const tempCtx tempCanvas.getContext(2d) tempCtx.font bold 60px Arial tempCtx.fillText(text, 0, 60) const particles [] const imageData tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height) // 每隔5像素取样一个粒子 for(let i0; iimageData.width; i5) { for(let j0; jimageData.height; j5) { const index (i j * imageData.width) * 4 if(imageData.data[index3] 128) { // 不透明像素 particles.push(new Particle( x i, y j, (Math.random()-0.5)*3, (Math.random()-0.5)*3, 2, rgb(${imageData.data[index]},${imageData.data[index1]},${imageData.data[index2]}), 100 )) } } } return particles }3. 性能优化实战经验当粒子数量超过500个时性能问题开始显现。经过多次测试我总结了这些优化方案对象池技术复用已销毁的粒子对象const particlePool [] class ParticleSystem { createParticle() { if(particlePool.length 0) { return particlePool.pop() // 复用已有对象 } return new Particle() // 新建对象 } recycleParticle(p) { particlePool.push(p) // 回收不再使用的粒子 } }分层渲染将静态背景与动态粒子分开绘制// 背景层canvas const bgCanvas document.createElement(canvas) const bgCtx bgCanvas.getContext(2d) // 绘制静态城市背景 drawCityBackground(bgCtx) // 主渲染循环 function animate() { // 先绘制静态背景 ctx.drawImage(bgCanvas, 0, 0) // 再绘制动态粒子 particleSystem.draw(ctx) requestAnimationFrame(animate) }动态分辨率根据设备性能调整粒子数量const quality detectDevicePerformance() // 自定义性能检测 const PARTICLE_COUNT quality high ? 1000 : quality medium ? 500 : 2004. 交互式新年贺卡完整实现将烟花系统封装成贺卡组件需要解决三个关键问题用户交互设计canvas.addEventListener(click, (e) { const rect canvas.getBoundingClientRect() const x e.clientX - rect.left const y e.clientY - rect.top // 从点击位置向上发射烟花 fireworks.push(new Firework(x, canvas.height, y)) }) // 移动端触摸支持 canvas.addEventListener(touchstart, (e) { e.preventDefault() const touch e.touches[0] const rect canvas.getBoundingClientRect() const x touch.clientX - rect.left const y touch.clientY - rect.top fireworks.push(new Firework(x, canvas.height, y)) })节日元素融合function drawFestivalElements() { // 绘制月亮 ctx.drawImage(moonImg, canvas.width-150, 50, 100, 100) // 绘制祝福语 ctx.font bold 40px Microsoft YaHei ctx.fillStyle gold ctx.textAlign center ctx.fillText(新年快乐, canvas.width/2, 80) // 绘制灯笼等装饰元素 drawDecorations(ctx) }自动播放与音乐同步// 预定义烟花发射序列 const fireworkSequence [ {time: 1000, x: 100, y: 300}, {time: 2500, x: 300, y: 200}, {time: 4000, x: 500, y: 250} ] let startTime null function checkAutoFireworks(timestamp) { if(!startTime) startTime timestamp const elapsed timestamp - startTime fireworkSequence.forEach(item { if(elapsed item.time !item.fired) { fireworks.push(new Firework(item.x, canvas.height, item.y)) item.fired true } }) } // 在动画循环中调用 function animate(timestamp) { checkAutoFireworks(timestamp) // ...其他渲染逻辑 }在实现过程中我发现音频同步是个难点。解决方案是使用Web Audio API精确控制播放进度const audioContext new (window.AudioContext || window.webkitAudioContext)() let audioBuffer null // 加载音频文件 fetch(firework.mp3) .then(res res.arrayBuffer()) .then(arrayBuffer audioContext.decodeAudioData(arrayBuffer)) .then(buffer { audioBuffer buffer }) function playSound(time) { const source audioContext.createBufferSource() source.buffer audioBuffer source.connect(audioContext.destination) source.start(time) }5. 常见问题与调试技巧在开发过程中我遇到过几个典型问题内存泄漏未及时清除不再使用的粒子导致内存增长// 错误做法直接push新粒子 particles.push(new Particle(...)) // 正确做法使用对象池 const p particlePool.get() p.reset(...) activeParticles.push(p)跨设备兼容性高DPI屏幕显示模糊需要根据devicePixelRatio调整canvas尺寸const scale window.devicePixelRatio || 1 canvas.style.width ${width}px canvas.style.height ${height}px canvas.width width * scale canvas.height height * scale ctx.scale(scale, scale)性能热点定位 使用Chrome DevTools的Performance面板记录运行时性能我发现粒子更新逻辑占用了85%的CPU时间。通过将粒子位置计算移入Web Worker主线程负载降低了40%。// worker.js self.onmessage function(e) { const particles e.data // 在worker线程中计算新位置 particles.forEach(p { p.x p.vx p.y p.vy // ...其他计算 }) postMessage(particles) } // 主线程 const worker new Worker(worker.js) worker.postMessage(particles) worker.onmessage function(e) { renderParticles(e.data) }调试Canvas动画时我习惯添加这些辅助工具代码// 显示FPS let lastTime performance.now() let frameCount 0 function showFPS() { const now performance.now() frameCount if(now - lastTime 1000) { console.log(FPS: ${frameCount}) frameCount 0 lastTime now } } // 在动画循环中调用 function animate() { showFPS() // ...其他逻辑 }

相关新闻