H5 Canvas粒子爆炸动画源码拆解:从粒子系统到可配置特效模块

发布时间:2026/10/15 21:13:58
H5 Canvas粒子爆炸动画源码拆解:从粒子系统到可配置特效模块
简介这是一份面向前端开发者与网页动效爱好者的H5 Canvas粒子爆炸动画特效源码包适合用于个人主页、活动页或产品展示页的背景动画开发帮助解决静态页面缺乏视觉冲击力的问题。压缩包共6个文件以3个JavaScript脚本为核心配合1个HTML与1个HTM页面、1个CSS样式表整体约59KB体积轻量便于直接嵌入现有项目。代码围绕Canvas 2D渲染上下文展开通过requestAnimationFrame驱动动画循环构建粒子系统模拟彩色粒子的爆炸、位移、变色与消散过程并可将文字轮廓拆解为粒子形成字符随爆炸动态聚合与散开的视觉效果。读者可从中学习粒子对象的属性定义、生命周期管理、重力与速度衰减等物理规则以及批量数组操作与闭包状态保持的写法。目前已有321人学习适合具备一定JavaScript基础、希望掌握高级前端动态效果制作技巧的开发者参考借鉴。1. 从一次页面改版说起这个粒子爆炸动画到底能干什么前阵子帮朋友改一个活动落地页对方丢过来一个压缩包说“就要那种点一下炸开的效果越炫越好但别太重”。打开一看就是这份 H5 Canvas 粒子爆炸动画特效。它干的事很纯粹在 Canvas 画布上生成一批粒子鼠标点击或页面加载时粒子从中心向四周炸开带拖尾、渐隐、重力回落最后自然消散。整个包不依赖任何框架纯原生 JavaScript 加 Canvas 2D 绘制丢进任何 H5 页面、活动页、游戏加载页都能直接用。适合谁一是做营销活动页、需要点击反馈动效的前端二是学 Canvas 动画、想搞懂粒子系统怎么写的初学者三是手里有现成项目想找个轻量特效模块嵌进去的开发者。它不解决业务逻辑只解决“视觉炸裂”这一件事但这件事它做得足够干净代码量小改起来不心疼。下面我按“能干什么 → 怎么跑 → 怎么改”的顺序把这份源码拆开讲。2. 核心架构拆解一个粒子从生成到消散的完整链路2.1 目录结构与文件职责拿到压缩包解压后结构通常很扁平常见做法是下面这样particle-explosion/ ├── index.html # 演示入口含 canvas 容器和基础样式 ├── css/ │ └── style.css # 页面重置、canvas 全屏铺满 ├── js/ │ ├── particle.js # 粒子类位置、速度、颜色、生命周期 │ ├── explosion.js # 爆炸控制器生成粒子、驱动动画循环 │ └── main.js # 入口绑定事件、初始化画布 └── README.mdindex.html只做两件事放一个canvas元素按顺序引入三个 JS。style.css把 body 的 margin 清零canvas 设成display:block避免出现滚动条——这是 Canvas 全屏动画最常见的翻车点很多人第一次跑发现右边和下边有白边就是没处理这个。particle.js是单个粒子的抽象explosion.js是粒子集合的管理器main.js负责把两者和 DOM 事件接起来。这种分层的好处是你想换爆炸形状只动explosion.js的生成逻辑想改粒子外观只动particle.js的绘制方法互不干扰。2.2 粒子类位置、速度与生命周期的三要素粒子系统的核心就三个量位置、速度、寿命。位置决定画在哪速度决定往哪飞寿命决定什么时候消失。看一段典型的粒子类实现// particle.js class Particle { constructor(x, y, options {}) { this.x x; // 当前横坐标 this.y y; // 当前纵坐标 this.vx options.vx || 0; // 横向速度分量 this.vy options.vy || 0; // 纵向速度分量 this.size options.size || 2; // 粒子半径 this.color options.color || #ff6b35; this.life 1; // 生命值1 为满血 this.decay options.decay || 0.015; // 每帧衰减量 this.gravity options.gravity || 0.05; // 重力加速度 } update() { this.vy this.gravity; // 纵向速度受重力影响 this.x this.vx; // 位置随速度更新 this.y this.vy; this.life - this.decay; // 生命值递减 } draw(ctx) { ctx.globalAlpha Math.max(this.life, 0); // 透明度跟随生命值 ctx.fillStyle this.color; ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); } isDead() { return this.life 0; } }update()每帧调用一次先加重力再更新位置顺序不能反——先更新位置再加重力粒子会显得“飘”下落感不自然。draw()里用globalAlpha控制透明度粒子越接近死亡越透明这是渐隐效果的关键。decay和gravity是最值得调的两个参数decay越大粒子消失越快gravity越大下落越明显。我一般把decay设在 0.01 到 0.02 之间gravity设在 0.03 到 0.08 之间具体看页面节奏。2.3 爆炸控制器批量生成与动画循环单个粒子没意义爆炸是一批粒子同时朝不同方向飞。控制器负责在触发点周围生成 N 个粒子每个粒子的速度方向按角度均匀分布// explosion.js class Explosion { constructor(canvas) { this.canvas canvas; this.ctx canvas.getContext(2d); this.particles []; this.rafId null; } burst(x, y, count 80) { for (let i 0; i count; i) { // 角度均匀分布保证炸开是个圆 const angle (Math.PI * 2 * i) / count; // 速度加随机扰动避免过于规整 const speed 2 Math.random() * 4; this.particles.push(new Particle(x, y, { vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, size: 1 Math.random() * 2, color: hsl(${Math.random() * 60 10}, 90%, 60%), decay: 0.012 Math.random() * 0.008 })); } this.start(); } start() { if (this.rafId) return; // 防止重复启动多个循环 const loop () { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // 倒序遍历方便删除死亡粒子 for (let i this.particles.length - 1; i 0; i--) { const p this.particles[i]; p.update(); p.draw(this.ctx); if (p.isDead()) this.particles.splice(i, 1); } if (this.particles.length 0) { this.rafId requestAnimationFrame(loop); } else { this.rafId null; // 粒子清空后停掉循环省性能 } }; this.rafId requestAnimationFrame(loop); } }burst()里角度用(Math.PI * 2 * i) / count均匀铺开保证爆炸是个完整的圆而不是一坨。速度加Math.random()扰动是为了避免所有粒子飞得一样远看起来像机械的同心圆。颜色用 HSL 的色相随机控制在暖色区间10 到 70出来的效果偏火焰橙红符合“爆炸”的直觉。start()里有两个细节值得说。一是if (this.rafId) return防止连续点击时启动多个requestAnimationFrame循环那样粒子会越跑越快是新手最容易踩的坑。二是粒子清空后把rafId置空并停止循环不然后台会一直空转页面卡顿的元凶往往就在这。2.4 入口绑定画布尺寸与事件接入main.js把画布尺寸和事件接起来// main.js const canvas document.getElementById(stage); const explosion new Explosion(canvas); function resize() { canvas.width window.innerWidth; canvas.height window.innerHeight; } resize(); window.addEventListener(resize, resize); canvas.addEventListener(click, (e) { const rect canvas.getBoundingClientRect(); explosion.burst(e.clientX - rect.left, e.clientY - rect.top, 100); }); // 页面加载后自动炸一次作为入场动效 window.addEventListener(load, () { explosion.burst(canvas.width / 2, canvas.height / 2, 120); });resize()里直接改canvas.width/height注意这会清空画布内容并重置坐标系所以要在初始化时调用一次。点击坐标用getBoundingClientRect()换算不能直接用e.clientX因为 canvas 不一定贴在页面左上角这是坐标偏移问题的根源。load事件里自动炸一次适合做首屏入场如果不需要删掉即可。3. 跑起来本地启动、参数调试与效果验证3.1 三种运行方式与选择这份源码是纯静态的不需要构建工具。最省事的方式是直接双击index.html浏览器就能跑。但如果你后续要改代码、加模块建议起个本地服务避免file://协议下某些浏览器对资源的限制。用 Python 起服务# 在项目根目录执行Python 3 自带 python -m http.server 8080 # 浏览器访问 http://localhost:8080用 Node 的话装个serve也行npx serve . # 默认 3000 端口按提示访问如果你打算把它嵌进现有项目直接把js/下三个文件和 canvas 标签搬过去注意引入顺序particle.js→explosion.js→main.js因为后者依赖前者的类定义。3.2 关键参数速查与调参方向调参是这份特效最常做的事把几个核心参数列出来改的时候心里有数参数位置默认范围调大效果调小效果countburst 调用处80~120粒子更多更密稀疏、性能更好speedexplosion.js2~6炸得更远聚在中心decayparticle.js0.012~0.02消失更快拖尾更久gravityparticle.js0.03~0.08下落明显悬浮感强sizeparticle.js1~3粒子更粗更细腻调参有个血泪经验别一次改好几个改一个刷新看一次。粒子效果是多个参数叠加出来的同时改容易找不到是哪个起的作用。我一般先定count和speed定整体形态再调decay和gravity定节奏最后微调size和颜色。3.3 验证效果是否正常跑起来后正常表现是页面加载后中心炸开一团暖色粒子向四周飞散并逐渐透明消失点击任意位置在点击处再炸一次。如果出现下面几种情况说明哪里不对粒子全部往一个方向飞检查angle计算是不是漏了Math.PI * 2粒子不消失越积越多检查isDead()和splice逻辑多半是decay没生效点击位置偏移检查有没有用getBoundingClientRect()换算连续点击越来越快检查start()里的rafId防重入判断。4. 避坑与常见问题排查4.1 粒子越点越多、页面越来越卡现象连续快速点击后粒子数量暴涨帧率明显下降。原因每次burst()都往this.particles里 push但没有上限控制点击频率高时粒子数会失控。解决在burst()开头加个上限判断比如if (this.particles.length 500) return;或者用对象池复用死亡粒子避免频繁创建销毁。4.2 画布模糊、粒子边缘发虚现象在高分屏上粒子看起来糊边缘不锐利。原因canvas 的 CSS 尺寸和实际像素尺寸不一致浏览器做了拉伸。解决按devicePixelRatio放大画布实际尺寸再用ctx.scale()缩放坐标系const dpr window.devicePixelRatio || 1; canvas.width window.innerWidth * dpr; canvas.height window.innerHeight * dpr; canvas.style.width window.innerWidth px; canvas.style.height window.innerHeight px; ctx.scale(dpr, dpr);4.3 页面切到后台再回来粒子瞬移或堆积现象切走标签页一段时间再切回粒子位置突变或一次性涌出。原因requestAnimationFrame在后台会暂停但update()里的速度是按帧累加的恢复后时间差没补偿。解决用时间戳计算 delta把速度乘以时间因子而不是每帧固定加。简单点也可以在visibilitychange事件里暂停和恢复动画。4.4 移动端点击没反应或延迟现象手机上点半天不炸或者炸的位置不对。原因移动端有 300ms 点击延迟且触摸事件和鼠标事件模型不同。解决改用touchstart或pointerdown事件pointerdown能同时覆盖鼠标和触摸是现在更推荐的做法。坐标换算逻辑不变e.clientX在 pointer 事件里同样可用。4.5 嵌入现有页面后样式冲突现象搬进项目后 canvas 尺寸不对或者被其他元素盖住。原因canvas 默认是 inline 元素受父容器 line-height 和字体影响且 z-index 没设。解决给 canvas 加display:block; position:fixed; top:0; left:0; z-index:9999; pointer-events:none;pointer-events:none让 canvas 不拦截点击这样它作为背景特效时不会挡住下面的按钮。5. 二次开发从固定爆炸到可配置特效模块把这份源码改造成能复用的模块是我拿到手后做的第一件事。原始代码把参数写死在类里换个页面就得改源码不优雅。我的做法是抽一个配置对象出来让调用方传参// 改造后的调用方式 const fx new Explosion(canvas, { count: 100, speedRange: [2, 6], decayRange: [0.012, 0.02], gravity: 0.05, hueRange: [10, 70], // 暖色 autoBurst: true // 是否加载后自动炸 }); // 想换冷色调只改配置 fx.setOptions({ hueRange: [180, 240] });配置化的关键是给每个参数一个合理的默认值调用方不传也能跑。hueRange控制色相区间暖色是 10 到 70冷色是 180 到 240改一个数组就能换风格比翻源码找颜色值高效得多。再进一步可以把爆炸形状从圆形改成其他图案。核心在burst()里生成速度向量的方式。圆形是角度均匀分布想改成心形就把角度和速度按心形参数方程映射// 心形爆炸的速度方向映射示意 const t (Math.PI * 2 * i) / count; const heartX 16 * Math.pow(Math.sin(t), 3); const heartY -(13 * Math.cos(t) - 5 * Math.cos(2*t) - 2 * Math.cos(3*t) - Math.cos(4*t)); const scale 0.3; this.particles.push(new Particle(x, y, { vx: heartX * scale, vy: heartY * scale, // ...其余参数 }));这样粒子会朝心形轮廓飞散形成心形爆炸。同理换成星形、文字轮廓都可以只要把目标形状的参数方程代进去。这是粒子系统好玩的地方形状逻辑和粒子逻辑解耦改形状不影响粒子的生命周期管理。验证改造是否成功我一般用三步先看默认配置下效果和原版一致再改一个配置项确认生效最后在移动端和桌面端各跑一遍确认没有平台差异。这套流程走下来基本能保证模块在别的项目里不会翻车。从那以后我每次拿到这类特效源码都强制先跑通默认效果、再抽配置、最后做跨端验证三步缺一不可。希望这份拆解帮到你拿到包能直接跑起来也能按自己的需求改出想要的样子。本文还有配套的精品资源点击获取