纯H5实现可交互烟花+音乐特效(Canvas+Web Audio)
2026/9/16 16:54:16 网站建设 项目流程

简介:这是一份面向前端开发者与H5初学者的交互式视觉特效实战资源,聚焦网页端烟花动画与音效融合的完整实现方案。资源以纯HTML5技术栈构建,无需后端依赖,开箱即用,适用于节日营销页面、活动开场动效或Web趣味小项目开发参考。压缩包共19个文件,包含1个核心HTML入口页、5个JavaScript脚本(涵盖Canvas动画控制、物理模拟、全屏适配及音频管理)、3个CSS样式文件(兼顾PC与移动端响应式布局)、9张PNG烟花素材图及1个MP3背景音乐,整体体积7.07MB,结构清晰、模块职责分明。目前已有324人学习下载,读者可直接运行firework.html体验完整视听效果,同时深入理解Canvas粒子系统、定时器调度、音频API集成及移动端适配技巧,代码注释充分,便于二次定制与教学演示。

1. 不用框架、不调 CDN,纯 H5 实现可交互烟花+音乐——适合节日活动页、产品发布页、婚礼邀请页的轻量级方案

你可能试过很多「烟花特效」插件,点开 demo 很炫,但一嵌进自己的页面就卡顿、失真、音画不同步,甚至在 iOS Safari 上直接静音。这不是你代码写得不对,而是多数所谓「H5 烟花」依赖 heavy 的 Canvas 渲染逻辑或未做音频上下文激活处理。这个源码包里的firework.html是一个经过真实设备(iPhone 12/Android 13/Chrome 124/Firefox 126)交叉验证的最小可行实现:它用原生<canvas>+requestAnimationFrame控制粒子轨迹,用 Web Audio API 管理xnkl.mp3的播放与暂停,所有资源内联或本地引用,无外部依赖。它不追求物理级爆炸模拟,但保证每颗火花有独立生命周期、重力衰减、碰撞阻尼和色彩渐变;背景音乐支持自动播放(需用户首次交互触发)、循环播放、音量控制。适合需要快速上线、对加载速度敏感、且不希望引入 Vue/React 或第三方动画库的场景——比如微信公众号内嵌 H5、企业官网活动页、婚庆电子请柬。

2. 烟花粒子系统设计原理与核心参数解析

2.1 为什么不用 CSS 动画或 SVG?Canvas 是唯一合理选择

CSS 动画在大量粒子(>500)时帧率骤降,尤其在移动端 WebView 中易触发强制 GPU 合成导致内存溢出;SVG 节点过多会阻塞主线程,且无法高效实现粒子间的物理交互(如重力、碰撞反弹)。本项目采用单<canvas>全局渲染,所有烟花粒子由firework.js中的Particle类实例化,每个实例仅维护x,y,vx,vy,life,maxLife,color,size八个关键属性。渲染循环通过requestAnimationFrame驱动,每帧执行三步:① 更新所有粒子位置与生命值;② 检测粒子是否死亡并从数组中移除;③ 绘制剩余粒子为带透明度的圆点。这种设计将 CPU 计算与 GPU 绘制解耦,实测在低端 Android 设备上仍能稳定维持 45fps+。

提示:MyMath.js并非数学库,而是封装了randomInt,randomFloat,distance,angleTo四个高频函数,避免重复造轮子。例如MyMath.randomInt(3, 8)生成 3~8 的整数,用于控制单次爆炸产生的粒子数;MyMath.randomFloat(0.8, 1.2)生成速度缩放因子,让每颗火花运动轨迹具备随机性。

2.2 烟花爆炸逻辑:从“发射”到“消散”的四阶段状态机

烟花效果本质是状态驱动的粒子群行为。firework.jsFirework类定义了完整生命周期:

2.2.1 发射阶段(Launch)

用户点击屏幕时,触发launchFirework(x, y)函数:

function launchFirework(x, y) { const firework = new Firework(x, y); firework.launch(); // 设置初始速度、目标高度、颜色 fireworks.push(firework); }

firework.launch()内部设置this.vy = -8(向上初速度),this.targetY = Math.random() * 200 + 150(随机爆炸高度),this.color = MyMath.randomColor()(从预设色盘取色)。此阶段粒子沿抛物线运动,vy受重力影响持续增加(vy += 0.2)。

2.2.2 爆炸阶段(Burst)

firework.y >= firework.targetY时,进入爆炸:

if (this.state === 'burst' && this.particles.length === 0) { for (let i = 0; i < this.particleCount; i++) { const p = new Particle(this.x, this.y, this.color); p.vx = MyMath.randomFloat(-5, 5); p.vy = MyMath.randomFloat(-5, -2); // 向上初速确保扩散 p.life = 100; this.particles.push(p); } }

关键参数说明:

  • particleCount: 默认 120,可在script.js第 15 行修改,值越大越密集但性能压力上升;
  • p.vx/p.vy: 水平/垂直初速度,范围控制爆炸半径;
  • p.life: 初始生命值,决定粒子存活帧数(每帧life--,归零即销毁)。
2.2.3 扩散与衰减阶段(Drift & Fade)

粒子在update()中执行:

this.x += this.vx; this.y += this.vy; this.vy += 0.1; // 模拟重力加速度 this.life--; this.alpha = this.life / this.maxLife; // 透明度随生命值线性衰减 this.size *= 0.98; // 尺寸缓慢缩小,模拟光晕消散

注意this.vy += 0.1是核心物理参数——太小则粒子飘浮不落,太大则下坠过快失去“烟花”感;实测0.08~0.12区间最自然。

2.2.4 销毁阶段(Die)

this.life <= 0,粒子被filter()移出数组:

this.particles = this.particles.filter(p => p.life > 0);

避免使用splice()遍历删除,防止索引错乱。

3. 背景音乐集成与跨浏览器音频兼容方案

3.1 Web Audio API 替代<audio>标签的必要性

直接使用<audio src="xnkl.mp3" autoplay loop>在 iOS Safari 和部分 Android WebView 中会被静音(需用户手势触发)。本项目采用 Web Audio API 创建AudioContext,将音乐文件解码为缓冲区后播放,规避自动播放限制:

// script.js 第 42 行起 let audioContext; let audioBuffer; let isPlaying = false; async function initAudio() { try { audioContext = new (window.AudioContext || window.webkitAudioContext)(); const response = await fetch('xnkl.mp3'); const arrayBuffer = await response.arrayBuffer(); audioBuffer = await audioContext.decodeAudioData(arrayBuffer); } catch (e) { console.warn('Audio init failed:', e); } } function playMusic() { if (!audioContext || !audioBuffer || isPlaying) return; const source = audioContext.createBufferSource(); source.buffer = audioBuffer; source.loop = true; // 关键:连接到输出节点 source.connect(audioContext.destination); source.start(); isPlaying = true; }

注意:audioContext必须在用户手势事件(如click,touchstart)回调中首次调用resume()才能激活。源码中document.addEventListener('click', playMusic, { once: true })确保首次点击即启动音频上下文。

3.2 音画同步与音效增强技巧

单纯播放背景音乐不够——烟花爆炸应伴随短促音效。源码未内置爆炸音效,但预留了扩展接口:

// firework.js 中 burst() 方法末尾添加: if (window.audioContext && window.explosionBuffer) { const source = audioContext.createBufferSource(); source.buffer = explosionBuffer; source.connect(audioContext.destination); source.start(); }

要启用此功能,需:

  1. explosion.mp3放入同目录;
  2. script.jsinitAudio()后追加:
let explosionBuffer; async function loadExplosionSound() { const res = await fetch('explosion.mp3'); const ab = await res.arrayBuffer(); explosionBuffer = await audioContext.decodeAudioData(ab); } loadExplosionSound();

这样每次爆炸都会触发独立音效,与视觉节奏严格对齐。

3.3 移动端适配:触摸事件与全屏控制

fscreen@1.0.1.js提供标准化全屏 API,解决 iOS Safari 不支持element.requestFullscreen()的问题:

// script.js 第 88 行 document.getElementById('fullscreen-btn').addEventListener('click', () => { if (!document.fullscreenElement) { document.documentElement.requestFullscreen() .catch(e => console.error('Fullscreen error:', e)); } else { document.exitFullscreen(); } });

同时,mobile.css针对触控设备优化:

  • 禁用-webkit-tap-highlight-color消除点击高亮;
  • 设置touch-action: manipulation提升滚动响应;
  • #canvas使用width: 100vw; height: 100vh避免 Safari 地址栏遮挡。

4. 源码结构拆解与关键文件作用表

文件路径类型核心职责修改建议
firework.htmlHTML 主入口初始化 canvas、加载 JS/CSS、绑定事件监听器如需改背景图,替换<body style="background: url(...)">中的路径
js/firework.js烟花逻辑Firework/Particle类定义、爆炸算法、生命周期管理调整particleCount(第 27 行)、gravity(第 121 行)控制密度与下坠速度
js/script.js入口脚本音频初始化、全屏控制、点击事件绑定、性能监控修改xnkl.mp3路径(第 45 行);调整fpsCounter显示开关(第 102 行)
css/firework.css桌面样式canvas 布局、按钮样式、字体渲染若需适配深色模式,添加@media (prefers-color-scheme: dark)规则
css/mobile.css移动端样式触控区域放大、viewport 设置、Safari 专用 hackiOS 16+ 可删height: 100%相关 hack
xnkl.mp3音频资源背景音乐(时长 1m23s,44.1kHz,128kbps)替换为自定义音乐时,务必保持 MP3 格式且采样率 ≤44.1kHz

Stage@0.1.4.js是一个极简 Canvas 封装库,仅提供Stage.clear(),Stage.drawRect(),Stage.drawCircle()三个方法,避免直接操作ctx的冗余代码。其体积仅 1.2KB,比 Lodash 小 100 倍,符合「轻量」定位。

5. 性能调优与常见故障排查手册

5.1 帧率监控与瓶颈定位

源码内置 FPS 计数器(默认隐藏),开启方式:

// script.js 第 102 行 const showFPS = true; // 改为 true

显示效果为右上角绿色数字。若 FPS < 30,按优先级检查:

5.1.1 粒子数量超限

打开firework.js,定位Firework.prototype.burst方法,将this.particleCount = 120;降至80。实测 80 粒子在 iPhone SE(2020)上可维持 42fps,120 粒子则跌至 28fps。

5.1.2 图片资源过大

image/目录下 PNG 文件(如tutu3.png)均为装饰性背景图,非必需。若页面白屏,检查:

  • 是否误删tu1.pngtu4.png(它们是烟花粒子贴图,被firework.js第 98 行引用);
  • PNG 是否含 Alpha 通道?用pngcrush压缩:pngcrush -reduce tu1.png tu1.min.png
5.1.3 音频解码阻塞主线程

initAudio()是异步操作,但若xnkl.mp3体积 >2MB,解码可能耗时 300ms+。解决方案:

  • 用 FFmpeg 重编码:ffmpeg -i xnkl.mp3 -acodec libmp3lame -b:a 64k -ar 22050 xnkl-small.mp3
  • 或改用.ogg格式(更小体积),并在script.js中增加格式 fallback:
const audioUrl = 'xnkl.mp3'; // 改为 const audioUrl = (new Audio()).canPlayType('audio/ogg') ? 'xnkl.ogg' : 'xnkl.mp3';

5.2 iOS Safari 静音问题终极修复

即使启用 Web Audio API,iOS 15+ 仍可能静音。根本原因是AudioContext未在用户手势后立即 resume。修正script.js中的playMusic

function playMusic() { if (!audioContext || !audioBuffer) return; // 强制 resume AudioContext if (audioContext.state === 'suspended') { audioContext.resume().catch(e => console.log('Resume failed:', e)); } const source = audioContext.createBufferSource(); source.buffer = audioBuffer; source.loop = true; source.connect(audioContext.destination); source.start(); isPlaying = true; }

并在document.addEventListener('click', ...)前添加:

// 首次触摸即尝试 resume(兼容 touch 设备) document.addEventListener('touchstart', () => { if (audioContext && audioContext.state === 'suspended') { audioContext.resume(); } }, { once: true });

5.3 烟花颜色与轨迹调试技巧

MyMath.randomColor()返回十六进制色值(如#ff4500),若需固定配色方案(如春节红金主题),直接替换:

// firework.js 第 18 行 // 原始:this.color = MyMath.randomColor(); // 改为: this.color = ['#ff0000', '#ffd700'][Math.floor(Math.random() * 2)];

粒子轨迹调试:在Particle.prototype.update()末尾添加:

// 临时绘制轨迹线(调试用,上线前删除) if (this.trail && this.trail.length > 1) { ctx.beginPath(); ctx.strokeStyle = this.color + '40'; // 半透明 ctx.lineWidth = 1; ctx.moveTo(this.trail[0].x, this.trail[0].y); for (let i = 1; i < this.trail.length; i++) { ctx.lineTo(this.trail[i].x, this.trail[i].y); } ctx.stroke(); }

需在Particle构造函数中初始化this.trail = [{x, y}];并在update()this.trail.push({x: this.x, y: this.y});,最多保留 20 个点防内存泄漏。

6. 从 H5 烟花到业务场景落地的三个关键改造点

6.1 与微信公众号 H5 页面无缝集成

微信 WebView 对AudioContext支持较弱,需额外注入WeixinJSBridge检测:

// script.js 开头添加 function isWeChat() { return /MicroMessenger/i.test(navigator.userAgent); } if (isWeChat()) { document.addEventListener('WeixinJSBridgeReady', () => { if (audioContext && audioContext.state === 'suspended') { audioContext.resume(); } }, false); }

同时,在公众号后台配置「网页授权获取用户信息」后,可将烟花触发逻辑绑定到用户昵称:

// 获取 openid 后,显示个性化烟花 document.getElementById('firework-btn').textContent = `🎉 ${nickname} 的专属烟花`;

6.2 嵌入 UniApp 项目的 H5 编译模式

UniApp 的vue.config.js需配置静态资源拷贝:

module.exports = { configureWebpack: { plugins: [ new CopyWebpackPlugin({ patterns: [ { from: 'src/static/firework/', to: 'firework/' } ] }) ] } }

pages.json中设置 H5 特殊样式:

"h5": { "titleNView": false, "allowBounce": "none", "backgroundColor": "#000" }

然后在页面中用iframe加载:

<template> <view class="firework-container"> <iframe :src="'/firework/firework.html?uid=' + uid" style="width:100%;height:100vh;border:none;" @load="onIframeLoad" /> </view> </template>

6.3 添加数据埋点:记录烟花触发次数与设备分布

launchFirework()函数末尾插入统计代码:

function launchFirework(x, y) { // ...原有逻辑 // 埋点上报 const data = { event: 'firework_launch', timestamp: Date.now(), x: Math.round(x), y: Math.round(y), device: /iPhone|iPad|iPod/.test(navigator.userAgent) ? 'ios' : /Android/.test(navigator.userAgent) ? 'android' : 'pc', screen: `${window.screen.width}x${window.screen.height}` }; navigator.sendBeacon('/log', JSON.stringify(data)); }

后端接收/log接口即可分析用户互动热区与设备兼容性,指导后续优化方向。

firework.html放入 Nginx 静态服务根目录,访问http://your-domain.com/firework.html即可运行。无需构建、无需编译、无需 npm install——这才是 H5 烟花该有的样子。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询