H5 Canvas粒子爆炸特效:从零实现到二次开发指南

发布时间:2026/10/15 10:13:14
H5 Canvas粒子爆炸特效:从零实现到二次开发指南 简介面向网页特效开发者的Canvas粒子爆炸动画示例基于HTML5 Canvas API实现彩色粒子文字背景通过JavaScript控制粒子生成、运动与消散让文字在爆炸与重组中呈现强烈的视觉冲击适用于个人主页、活动专题页等场景的动效装饰。压缩包共6个文件包含3个JS脚本、1个HTML页面、1个CSS样式表及1个htm说明文件整体体积约59KB结构清晰可直接在浏览器中运行并查看效果。已有321人学习下载。代码中引入EaselJS、PreloadJS等辅助库粒子系统、动画循环与文字轮廓绑定等核心逻辑均封装在JS文件中适合想深入理解Canvas动画原理、学习高性能前端特效实现方式的中级开发者研读与二次改造。1. 粒子爆炸这个H5特效为什么值得自己写一次在浏览器里做一次“爆炸”比多数人想的要便宜得多——不依赖物理引擎、不需要 WebGL一个 2D 画布加几十行 JavaScript 就能让点击处迸出几百颗彩色粒子而真正决定“炸不炸”的往往不是粒子数量而是初速度、阻尼和重力这三个值的匹配。这套 H5 Canvas 粒子爆炸动画特效就是按这个思路组织的一段纯 Canvas JS 特效代码整包零第三方依赖从事件绑定到粒子生成、渲染、回收是一条完整可运行的链路。适合三类人直接上手需要点击反馈的 H5 活动页、想给个人站点加交互彩蛋的前端开发者以及打算用最简代码理解粒子系统原理的人——它既是可用成品也是很好的教学切片值得拿到手之后亲自跑一遍再改两版。2. 包结构与启动零依赖JS特效的第一步2.1 解压后包里有哪些文件我把这个 zip 解压后第一件事是看目录。典型结构是典型的 H5 单页应用布局页面、样式、脚本分三个目录放资源之间用相对路径引用不依赖后端也不需要编译h5-canvas-particle-boom/ ├── index.html # 入口页面包含 canvas 节点和脚本引用 ├── css/ │ └── style.css # 页面布局与 canvas 定位样式 ├── js/ │ ├── particle.js # 粒子类坐标、速度、生命周期与绘制 │ ├── explosion.js # 爆炸生成器初始化粒子、随机发散参数 │ ├── boom.js # 特效主体canvas 上下文管理、动画循环 │ └── main.js # 事件绑定和启动入口 └── assets/ └── placeholder.txt # 预留资源目录可放背景图或音效index.html 是唯一需要打开的页面里面只放了一个全屏 canvas 节点和样式表、脚本的引用。style.css 的作用很朴素把 canvas 铺满视口、设置 position 和 overflow 让页面不出现滚动条。四个 JS 文件的职责边界我拆的时候看得很清楚——particle.js 只管单个粒子怎么动explosion.js 只管一批粒子怎么生成boom.js 管画布和循环main.js 管用户怎么触发。这种按职责拆文件的方式对我后面做二次开发非常友好想改粒子形态不用去动画循环里翻代码。2.2 技术选型为什么是 Canvas 2D 而不是别的这个包走的是浏览器原生方案核心是 Canvas 2D 的context接口配合requestAnimationFrame驱动渲染循环。整套代码里没有出现 WebGL、没有引入 particles.js 或 d3-force 这类第三方库甚至没有 jQuery。选 Canvas 2D 的理由很直接粒子爆炸是典型的“大量小图形、短生命周期、不需要持续留存画面”的场景2D 画布的arc画圆和fillRect画方块已经能覆盖WebGL 在这种粒子数量下属于杀鸡用牛刀除非同时跑几千上万粒子且每个都带纹理否则看不出明显优势。不引第三方库的好处是可控。粒子特效在真实项目里最容易遇到的问题就是“调参玄学”——同样的效果几行原生代码你可以精确知道每一帧发生了什么而一旦套上抽象的粒子库查 bug 时要先翻它的生命周期钩子得不偿失。这套代码把物理过程暴露给你速度衰减值是多少、重力加速度是多少、颜色怎么抽全在变量里打开控制台改一个数字就能看到效果变化这种“所见即所得”的调试体验是库给不了的。2.3 怎么把特效跑起来这个包是纯静态资源但我不建议直接双击 index.html 打开。因为有更新逻辑用的是相对路径脚本直接在file://协议下打开容易踩到浏览器对本地模块的 CORS 限制多数情况下页面会白屏控制台报跨域错。本地起一个静态服务是最稳的方式三选一即可# 方式一如果你电脑有 Python cd h5-canvas-particle-boom python3 -m http.server 8080 # 浏览器访问 http://localhost:8080 # 方式二如果你有 Node 环境用 npx 一行起服务 npx serve . # 方式三用了 VS Code 的话装 Live Server 插件直接右键 index.html起服务后打开页面页面展示一张全屏 canvas鼠标或手指点到哪里哪里就炸一团彩色粒子粒子飞散后逐渐减速、下落、淡出整个过程大概 1 秒到 1.5 秒爆炸数量、速度、颜色在代码里都有配置入口。验证是否跑通的标准很简单打开浏览器控制台切到 Performance 面板点击页面时能看到动画帧率稳定在 60 帧附近同时 Network 面板没有任何报错请求——这说明 Canvas 渲染循环和事件监听都已经正常工作了。3. 核心实现拆解一个粒子从生到死的完整链路3.1 粒子对象模型每个粒子自己知道怎么动看代码我习惯先找最小单元。这个包里最小单元是Particle类它把一次爆炸中单个粒子的全部状态收敛进一个对象当前位置、速度、尺寸、颜色、剩余生命。字段不多但每一个都直接影响爆炸的观感我把最核心的类实现还原在下面class Particle { constructor(cx, cy) { this.x cx; // 粒子出生点 x 坐标来自爆炸中心 this.y cy; // 粒子出生点 y 坐标 const angle Math.random() * Math.PI * 2; // 0~360 度随机方向 const speed 2 Math.random() * 6; // 初速度 2~8范围决定炸开半径 this.vx Math.cos(angle) * speed; // x 方向分速度 this.vy Math.sin(angle) * speed; // y 方向分速度 this.life 1; // 剩余生命1 为满0 为消亡 this.decay 0.012 Math.random() * 0.008; // 每帧衰减量 0.012~0.02 this.size 1 Math.random() * 3; // 粒子半径 1~4 像素 this.color COLORS[Math.floor(Math.random() * COLORS.length)]; // 从调色板随机选色 } update() { this.x this.vx; // 按速度移动 this.y this.vy; this.vy 0.08; // 重力常数让粒子边飞边下落 this.vx * 0.985; // 阻尼系数速度逐帧衰减 this.vy * 0.985; 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(); } }update和draw分开是这套代码最值得学习的地方update只处理物理运算不碰任何绘制逻辑draw只消费当前状态值不改数据。这样拆分后你想把圆形粒子改成方块只需改draw想让粒子飞得更飘只需调vy 0.08里那个重力值。参数说明初速度 2 到 8 的随机区间决定了爆炸的“喷射感”低于 2 粒子会原地掉落高于 10 粒子会飞出屏幕边界阻尼 0.985 是每帧乘一次代表空气阻力数值越接近 1粒子飞得越远越久。3.2 爆炸生成器一大团粒子怎么散开有了单个粒子下一步是“一次爆炸生成一堆粒子”。explosion.js里的生成逻辑不复杂核心是循环调用粒子类构造器再配合一个调色板数组让颜色有变化。我一般会在原本代码基础上加一个粒子数量参数方便不同场景控制密度function explode(cx, cy, count 120) { for (let i 0; i count; i) { particles.push(new Particle(cx, cy)); } } const COLORS [ #ff6b6b, #feca57, #48dbfb, #ff9ff3, #f368e0, #1dd1a1 ];关键不在循环本身而在构造函数里那个随机角度与速度范围的配合。角度用Math.random() * Math.PI * 2是为了让粒子均匀铺满 360 度如果写死成 45 度角的倍数爆炸出来就会是放射线而不是一团雾。速度用“最小值加范围随机”而不是整段乱随机是为了保证既有快速冲出的外圈粒子也有慢慢飘的内圈粒子视觉上有层次感。颜色数组里放 6 个高饱和色红色、金色、蓝色、粉色混在一起比单色粒子更像真实的烟花爆炸在实际项目中你可以直接替换成品牌色这个数组就是为二开留的接口。3.3 动画循环与事件绑定点击位置如何变成爆炸中心粒子都在了还差两个驱动一是每帧更新并重绘画布二是把用户点击换算成画布坐标并触发爆炸。动画循环写在boom.js里默认用requestAnimationFrame而非setInterval因为前者能自动匹配屏幕刷新率显示器 60Hz 就每秒跑 60 帧页面切到后台时自动暂停省电也不卡function tick() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 清空上一帧画面 for (let i particles.length - 1; i 0; i--) { const p particles[i]; p.update(); // 先更新物理状态 if (p.life 0) { // 生命耗尽就回收避免数组无限膨胀 particles.splice(i, 1); continue; } p.draw(ctx); // 存活粒子才画 } requestAnimationFrame(tick); // 请求下一帧 } canvas.addEventListener(pointerdown, function (e) { const rect canvas.getBoundingClientRect(); const x e.clientX - rect.left; // 鼠标坐标转画布本地坐标 const y e.clientY - rect.top; explode(x, y); });倒序遍历数组再splice是个小技巧正序删除会漏掉后移的元素倒序删除则每删一个都不影响前面还没遍历到的下标这是借删除粒子实现对象池收缩的常见做法。pointerdown是这套代码故意选的click在移动端有几百毫秒的延迟mousedown不支持触摸屏而pointerdown统一了鼠标和手指反应最快。坐标换算里的getBoundingClientRect()必须在每次点击时现取不能缓存否则页面滚动或 canvas 位移后爆炸位置会偏——这一点在避坑章节我还会专门展开。4. 从Demo到可交付参数配置、事件接入与性能观察4.1 把魔法数字收敛成一份配置原版代码把速度、数量、颜色写死在类里直接用没问题但接进业务页面后你会很快发现活动页要爆炸密度小一点、官网要速度慢一点、个人主页想要品牌色总不能每次改动都去翻粒子类。更合理的做法是把这些魔法数字收敛成一份CONFIG对象对外暴露修改入口const CONFIG { count: 120, // 单次爆炸粒子总数 speedMin: 2, // 最小初速度 speedMax: 8, // 最大初速度 gravity: 0.08, // 重力加速度 drag: 0.985, // 阻尼系数 decayMin: 0.012, // 生命衰减下限 decayMax: 0.02, // 生命衰减上限 sizeMin: 1, // 粒子最小半径 sizeMax: 4, // 粒子最大半径 colors: [#ff6b6b, #feca57, #48dbfb, #ff9ff3] };改造后的粒子构造器直接读这份配置比如速度变成CONFIG.speedMin Math.random() * (CONFIG.speedMax - CONFIG.speedMin)。下面这张参数表是实际调参时的参考也是我建议你提交代码时写进注释里的参数取值范围对效果的影响count60 ~ 300粒子太少显得稀疏太多低端机会掉帧speedMin / speedMax1 ~ 12决定爆炸的喷射半径和冲击感gravity0 ~ 0.2让粒子下落0 则粒子悬浮不落drag0.95 ~ 0.995越接近 1 粒子飞得越远越小越像浓烟decayMin / decayMax0.005 ~ 0.05数值越大粒子消失越快把配置单独拎出来最大的价值是可以同时支持“设计调视觉”和“开发改代码”两条线设计给一份取值建议开发改一行配置即可不需要动粒子类。4.2 给业务方一个干净的调用入口配置说完再说怎么把它变成一个能被业务侧直接调用的能力。原工程里main.js直接绑定了 canvas业务方想再加一个“页面加载时自动在中心炸一次”就得改源码。我习惯在boom.js之上再包一层 Facade对外只暴露两个方法——bind(canvas, config)和explodeAt(x, y)。绑定和触发解耦之后任何按钮、定时器、动画回调都能触发表情特效不用关心内部实现。export function bind(canvas, config {}) { Object.assign(CONFIG, config); const ctx canvas.getContext(2d); let particles []; // 内部复用上一节的 tick 循环 } export function explodeAt(x, y) { explode(x, y, CONFIG.count); }这层封装在实际项目里非常实用。比如一个 H5 落地页注册按钮点击后调用explodeAt(buttonCenterX, buttonCenterY)可以把视觉反馈绑到任意业务动作上配合 CSS 动画把 canvas 位置固定成绝对定位特效层和业务层彻底分离业务页面完全感知不到特效代码的存在。4.3 性能观察什么时候算“能上线”特效跑通只是第一步能稳定跑才叫可交付。我给自己定的标准是在低端 Android 机型上连续快速点击 20 次页面不掉到 50 帧以下内存不持续增长。验证方式不靠感觉打开浏览器 DevTools 的 Rendering 面板勾选 FPS Meter然后快速连点 canvas——如果帧率曲线有断崖优先检查两个点第一个是粒子数量单次爆炸超过 200 粒时对手机 canvas 填充开销明显第二个是 canvas 的物理像素尺寸全屏 canvas 在高分屏下实际渲染像素是 CSS 尺寸的好几倍粒子多的时候会很吃力。5. 避坑清单粒子爆炸最常见的五个翻车现场5.1 粒子飞出去是直线不像爆炸现象是点击后粒子沿着几条固定的方向飞画面呈现出放射线状的“呲水枪”效果非常不自然。原因几乎都出在随机角度没写对要么用了写死的角度数组要么随机函数写成了Math.random() * 5 * Math.PI这种只覆盖半圈的写法。解决方法是确保角度覆盖完整 360 度并加一点抖动Math.random() * Math.PI * 2是最稳的写法如果你希望某个特定方向粒子更密比如向上喷射可以用高斯随机让角度集中在 90 度附近而不是砍掉其他方向。5.2 爆炸后有残影画面慢慢发白现象是爆炸完粒子拖出一条长尾巴反复点击后整个 canvas 变白。原因是清屏没清干净要么clearRect的尺寸参数没跟上 canvas 的实际宽高要么在高分屏适配时使用了ctx.scale(dpr, dpr)放大画布却没有在清屏时做对应的坐标系换算。正确做法是每帧清屏前先重置变换矩阵把dpr缩放留到绘制时单独处理或者统一用canvas.width / canvas.height物理像素尺寸配合ctx.setTransform(1, 0, 0, 1, 0, 0)再清屏。这个毛病我栽过不止一回代码里凡是出现scale的地方都要对clearRect特别敏感。5.3 移动端点击没反应或反应极慢现象是桌面端鼠标点击完全正常但手机上手指点上去要么没反应要么隔了一拍才爆。原因大概率是事件监听用了click。移动端浏览器为了区分单击和双击会给click加几百毫秒延迟而mousedown在触摸屏上有时不触发。解决方法是统一改用pointerdown这是现代浏览器为鼠标、触摸、触控笔统一设计的事件响应速度接近原生触摸如果你要兼容很老的内核再加一个touchend兜底注意事件内不要遗漏e.preventDefault()防止页面在触摸时缩放或滚动。5.4 页面滚动后爆炸位置跑偏现象是页面在顶部时点击位置很准一旦往下滚动一段再点粒子从距离手指一段距离的地方冒出来。原因是事件里拿到的clientX/clientY是相对浏览器窗口的而 canvas 在页面中的位置会随滚动变化如果直接把窗口坐标当画布坐标用canvas 越靠下偏差越大。解决方法是每次点击时用canvas.getBoundingClientRect()重新取 canvas 左上角相对视口的位置再计算x e.clientX - rect.left、y e.clientY - rect.top。记住getBoundingClientRect()返回的坐标本身就会随滚动实时变化所以必须在事件回调里现取现算不要存缓存。5.5 连续点击后越来越卡页面发热现象是点得越多越卡最后帧率掉到个位数。原因是粒子数组只进不出构造函数往全局数组push但粒子生命周期结束后没有及时移出数组数组无限膨胀每帧都要遍历几千个已经不显示的对象。解决方法是像第 3 章动画循环里写的那样倒序遍历数组并在life 0时splice移除更进一步可以用对象池把消亡粒子的对象回收复用减少 GC 抖动。我一般会同时加一道保险if (particles.length 2000) particles.splice(0, 500)把数组长度压个上限极端情况下牺牲部分视觉换流畅度。这个上限值在手机上我习惯取 1200 到 1500桌面端可以放到 3000。6. 二次开发文字粒子、拖尾与目标图案实战这套代码最爽的地方在于每个模块都可以单独拆出来改。第 3 章说过Particle的update和draw分离所以做文字爆炸不需要动物理逻辑只需要在生成粒子前先确定目标坐标。一个非常经典的玩法是“文字粒子”离屏画布画几个字用getImageData读取像素点坐标把这些坐标作为粒子初始位置爆炸时粒子从文字形状向四周飞散function explodeText(x, y, text) { const off document.createElement(canvas); off.width 300; off.height 100; const octx off.getContext(2d); octx.font bold 48px sans-serif; octx.textAlign center; octx.textBaseline middle; octx.fillStyle #fff; octx.fillText(text, 150, 50); const imageData octx.getImageData(0, 0, 300, 100); const positions []; for (let py 0; py 100; py 2) { // 步进 2 减少采样量 for (let px 0; px 300; px 2) { const alpha imageData.data[(py * 300 px) * 4 3]; if (alpha 128) positions.push([px - 150 x, py - 50 y]); } } positions.forEach(([px, py]) particles.push(new Particle(px, py))); }步进 2 是为了把采样量降到四分之一避免在文字笔画密集处一次生成上万个粒子直接把低端机打挂透明度阈值 128 只选取文字覆盖区的像素抗锯齿边缘的半透明像素自动跳过。想让粒子带拖尾把每帧的clearRect改成ctx.fillStyle rgba(0, 0, 0, 0.1); ctx.fillRect(...)画面就不会完全清空上一帧残留会形成流动的轨迹想炸出特定图案提前把目标图案的像素坐标算好存成数组生成时直接复制坐标即可。有件事我印象深刻有一版活动页我把粒子数量一路调到 8000桌面端看非常震撼结果上线前在某款中端手机上直接掉到 12 帧整页交互都跟着卡。从那以后我每次调完视觉参数都强制自己在 2 倍长宽比模拟加低端机型帧率面板里走一遍粒子数量和物理参数再炫也要先过性能这一关。整套 H5 Canvas 粒子爆炸动画特效最大的价值不在于某个惊艳效果而是提供了一个可以直接改造的起点——从换颜色、换形状到换触发方式每一步都有清晰的改动位置。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询