资讯动态

烟花代码11种写法怎么选?canvas、CSS、jQuery实现原理与避坑指南

发布时间:2026/10/26 1:01:29 来源:尧图企业网站定制
简介11套基于HTML、jQuery与CSS实现的烟花代码合集面向前端初学者、网站设计师及网页视觉爱好者可快速在页面中营造节日或庆典氛围也可作为canvas动画、事件交互和样式过渡的实战参考。压缩包共59个文件其中21个js脚本负责粒子轨迹计算、随机参数生成和交互逻辑12个html页面搭建展示结构与运行入口4个css样式表控制颜色、透明度和动画过渡再配合png图片、json配置等资源整体仅4.6MB轻量易用。当前已有2983人参与学习下载覆盖了从简单canvas模拟到函数封装的不同实现层次。每套代码实现思路各有侧重有的完全用JavaScript绘制烟花的升空、爆炸与消散有的基于HTML5 canvas逐帧渲染粒子系统有的结合CSS3的transform和transition模拟绽放过程甚至通过keyframes定义连续动画通过对照研读可以系统掌握canvas绘图基础、jQuery事件与动画API、requestAnimationFrame流畅循环、随机数物理模拟、性能优化及多屏适配等关键技能适合逐步改造并整合进个人或商业项目。1. 为什么“烟花代码”会有11种写法先搞清楚你要的是哪种烟花碰到节日想给页面加点气氛搜“烟花代码”总会翻到类似标题的合集。这类合集本质上是同一件事——用Html、jQuery、css这些前端手段把烟花爆炸拆成升空、炸开、拖尾、熄灭几个阶段然后每种实现方式各写一套。于是同一个效果有的版本打开是深色画布、鼠标一点就开花有的版本纯css就能自己循环播放还有的版本需要先引入一个jQuery文件才能跑。问题往往出在这里你复制到的代码可能依赖老版本库、可能打开就是白屏、可能粒子一多就卡成幻灯片。这篇笔记的目标很直接把“烟花代码”背后的实现方式拆开讲清楚让你到手一套能跑、能改、能移植到自己页面里的方案也让你以后再看到这类合集时一眼能判断该选哪一份。适合想把页面做得有记忆点、但不想花太多时间在前端细节上的读者。2. 从渲染原理看选型canvas、CSS动画、jQuery DOM操作各自的边界2.1 三种实现方式各自的渲染路径先看它们到底在浏览器里做了什么这决定了你该选哪一套。canvas 方案是“像素级绘制”。画布上没有一个真实的DOM节点所有烟花粒子都是你手动算坐标、画圆点。它最大的优势是粒子数量可控、性能上限高。一个canvas上同时存在几百个粒子每帧只要重绘一遍画布浏览器不需要管布局和样式自然跑得动。代价是它不认识“点击”“文字”这些页面概念你要自己在坐标上做命中判断要在画布上写字也得用fillText。CSS 动画方案是“样式级动效”。爆炸火花是真实的DOM元素比如span或i标签用transform、opacity、animation控制它们从中心飞散。因为现代浏览器会把transform和opacity动画交给GPU合成不需要回流和重绘整个页面所以少量粒子的CSS烟花非常流畅。代价是写死方向、写死半径随机性很难做想炸出50个方向就要写50条nth-child规则代码量会失控。jQuery DOM 操作方案是“元素级移动”。每个火花都是一个div或span用.animate()改变left、top、opacity。它的优点是想放什么内容都行——汉字、图标、小表情都能当烟花粒子飞出去缺点是DOM一多就触发大量reflow页面帧率会迅速掉下来所以只适合1020个粒子的“小烟花”不适合满天星。2.2 选型判断表你手头的场景该用哪一套我做这类效果时一般先问自己三个问题粒子多不多、要不要文字、跑在什么设备上。答案组合起来基本就定了。场景推荐方案理由桌面端网页背景彩蛋canvas粒子量大也不怕随机性容易做移动端H5首页氛围装饰CSS动画利用合成器优化低端机也不容易卡文字烟花、祝福语飞散jQuery或canvasfillText需要把文本当作粒子载体页面里只炸三五个小烟花CSS或jQuery代码短不需要引入额外逻辑已有完整项目不想动结构canvas独立覆盖层用fixed定位挂一层画布不干扰业务DOM判断一份合集里的代码属于哪类实现不用看完整文件。打开HTML源码找三个特征script里有没有getElementById(canvas)或者ctx.beginPath——有就是canvas样式表里有没有大量keyframes关键帧、nth-child——有就是纯CSS动画脚本里有没有$()或.animate()——有就是jQuery DOM方案。10秒定位不用一步步读代码。2.3 为什么合集会同时收录11套不是重复是边界不同经常有人问一个烟花效果做一套不就完了实际上“一套”只能覆盖一类场景。纯CSS那套适合塞进不加载任何JS的静态页面canvas那套适合做整页背景jQuery那套只有一个文本数组就能改成“新年快乐”四个字飞出去。合集的价值在于同一套爆炸逻辑换一种载体适用的页面和交互方式就完全不同。所以标题里写“总有一款是你喜欢的”不是营销话术是真的每种实现都有人需要。你只需要从本文往下找到和你场景最像的那一款改参数即可。3. 用canvas把烟花从零跑起来最小可复现代码与参数说明3.1 一套可直接运行的canvas烟花下面这份是完整可运行的HTML文件复制保存为fireworks.html双击打开就是一朵朵自动升空爆炸的烟花点击页面任意位置也会在点击处炸一朵。我刻意把代码控制到最小规模方便你看清一条烟花的完整生命周期。!doctype html html langzh-cn head meta charsetutf-8 titlecanvas 烟花最小实现/title style html, body { margin: 0; padding: 0; height: 100%; overflow: hidden; background: #0a0e1a; } canvas { display: block; } /style /head body canvas idfireworks/canvas script var canvas document.getElementById(fireworks); var ctx canvas.getContext(2d); var W canvas.width window.innerWidth; var H canvas.height window.innerHeight; var particles []; var MAX_PARTICLES 600; // 从底部发射一颗升空烟花 function launch(fromX, fromY) { var p { x: fromX, y: fromY, vx: (Math.random() - 0.5) * 0.8, vy: -7 - Math.random() * 4, // 初速度向上负值越大升得越高 targetY: fromY - 140 - Math.random() * 200, // 爆炸点的目标高度 type: rise }; particles.push(p); } // 在指定位置爆炸生成一圈火花 function explode(x, y) { var count 40 Math.floor(Math.random() * 30); // 火花数量 for (var i 0; i count; i) { var angle Math.random() * Math.PI * 2; var speed 1.5 Math.random() * 3.5; particles.push({ x: x, y: y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, life: 50 Math.floor(Math.random() * 30), // 火花寿命单位帧 color: hsl( Math.floor(Math.random() * 360) , 90%, 60%), type: spark }); } } function tick() { // 用半透明背景覆盖留下上一帧残影形成拖尾 ctx.fillStyle rgba(10, 14, 26, 0.22); ctx.fillRect(0, 0, W, H); for (var i particles.length - 1; i 0; i--) { var p particles[i]; p.x p.vx; p.y p.vy; if (p.type spark) { p.vy 0.06; // 重力让火花走抛物线 p.life - 1; if (p.life 0) { particles.splice(i, 1); continue; } ctx.globalAlpha Math.max(0, p.life / 60) * 0.9; ctx.beginPath(); ctx.arc(p.x, p.y, 1.5, 0, Math.PI * 2); ctx.fillStyle p.color; ctx.fill(); } else if (p.y p.targetY) { // 升空到位炸开 explode(p.x, p.y); particles.splice(i, 1); } else { ctx.globalAlpha 1; ctx.beginPath(); ctx.arc(p.x, p.y, 2, 0, Math.PI * 2); ctx.fillStyle #ffd27a; ctx.fill(); } } ctx.globalAlpha 1; requestAnimationFrame(tick); } // 点击页面任意位置在鼠标处发射 canvas.addEventListener(click, function (e) { launch(e.clientX, e.clientY); }); // 每 1.2 秒自动从底部随机位置发射一朵 setInterval(function () { if (particles.length MAX_PARTICLES) return; launch(Math.random() * W, H 5); }, 1200); tick(); window.addEventListener(resize, function () { W canvas.width window.innerWidth; H canvas.height window.innerHeight; }); /script /body /html这段代码的逻辑可以拆成三段看。第一段是发射升空launch创建了一个rise粒子初始位置在页面底部vy是向上的负速度targetY是它爆炸的目标高度。第二段是爆炸explode在升空粒子到达targetY时被调用它按随机角度生成一圈火花每个火花有自己的速度和寿命。第三段是逐帧更新tick函数用requestAnimationFrame驱动每帧先刷新背景再更新所有粒子的位置、重力和生命周期最后把粒子画到画布上。几个关键参数直接决定了烟花好不好看。vy在-7到-11之间时升空速度比较自然太低会显得烟花很矮太高会快得看不清轨迹。targetY的爆炸高度我设为从发射点往上140到340像素这个区间适合多数屏幕。火花的count是40到70个太少炸开不饱满太多在低端机上会吃力。speed是火花初始速度1.5到5这个范围炸开半径大约覆盖60到120像素。life是火花存活帧数50到80帧配合重力0.06拖尾和抛物线效果最接近真实烟花。最后一个需要多说的是背景填充rgba(10, 14, 26, 0.22)透明度越低拖尾越长越高拖尾越短0.2左右是经验值。3.2 只保留自动播放或只保留点击触发如果你要把它挂到博客上默默播放不想让用户点击才出效果就把click事件监听那两行删掉保留setInterval自动发射。反过来如果你想做成“用户点哪里哪里炸”就把setInterval整段删掉只保留点击事件。我通常两种都保留因为自动播放负责氛围点击触发负责互动用户不会觉得页面死板。注意点击触发有个前提——canvas铺满全屏时点击事件是canvas在接收。如果你把它嵌到一个已有页面里铺满的canvas会挡住下层按钮的点击。这个问题在第5章和第6章会专门讲解决方案核心思路是让canvas不拦截鼠标事件。3.3 控制粒子总数防止内存和帧率一起崩上面代码里有MAX_PARTICLES这个常量当粒子总数超过600时新launch会被拒绝。很多人第一次写canvas烟花时容易忽略这个自动播放30秒后页面突然卡顿就是因为粒子数组里堆了几千个对象每次tick都要全部遍历并重新绘制。600的上限是桌面端和移动端都比较安全的数值如果你做的效果火花量特别大可以把上限放宽到1000但建议同时把花火的单个半径缩小一点减少像素填充量。4. 不止canvas纯CSS爆炸动画与jQuery文字烟花的可替换方案4.1 纯CSS实现爆炸火花固定方向、靠延迟制造层次纯CSS烟花的思路和canvas完全不同火花是真实DOM元素爆炸是一个从中心向外飞散的transform动画。优点是代码不需要任何JS就能循环播放适合放在静态页面顶部或H5邀请函里当氛围装饰缺点是不好做随机方向是写死的。下面这套是我常用的“8方向双层火花”做法HTML里放一个容器CSS负责升空和爆炸两个阶段用animation-delay衔接div classcfw idcfw1/div.cfw { position: fixed; left: 50%; top: 65%; width: 0; height: 0; } .cfw .fire-line { position: absolute; left: -3px; top: -3px; width: 6px; height: 6px; border-radius: 50%; background: #ffd27a; opacity: 0; animation: fireRise 1.1s ease-out forwards, firePop 0.9s ease-out forwards; animation-delay: 0s, 1.1s; } .cfw .spark { position: absolute; left: -2px; top: -2px; width: 4px; height: 4px; border-radius: 50%; background: var(--c); opacity: 0; animation: firePop 0.85s ease-out forwards; animation-delay: var(--d); transform: rotate(var(--deg)) translateX(0); } keyframes fireRise { 0% { opacity: 1; transform: translateY(0); } 100% { opacity: 1; transform: translateY(-240px); } } keyframes firePop { 0% { opacity: 1; transform: rotate(var(--deg)) translateX(0) scale(1); } 100% { opacity: 0; transform: rotate(var(--deg)) translateX(72px) scale(0.4); } }关键点在firePop这个关键帧它同时使用了rotate和translateX而且rotate必须写成一个固定角度否则动画过程中角度会被重置火花会全部飞到同一个方向去。deg、d、c这几个CSS变量是在生成火花DOM时通过style赋值的方向一样、延迟不同炸开时就有了先内圈后外圈的层次感。生成火花DOM的过程我用一小段JS来做因为纯手写16个div太蠢了var container document.getElementById(cfw1); var colors [#f66, #fc6, #6c9, #69f, #f9f, #ff8]; for (var i 0; i 16; i) { var spark document.createElement(span); spark.className spark; var deg i * 22.5; // 360度均分16个方向 var dist i % 2 0 ? 72 : 52; // 内外两圈交替 spark.style.setProperty(--deg, deg deg); spark.style.setProperty(--d, (1.1 (i % 2) * 0.08) s); spark.style.setProperty(--c, colors[i % colors.length]); container.appendChild(spark); }这段JS只做了DOM生成这件事动画全程仍然由CSS接管。每个spark的animation-delay都大于1.1秒正好等fire-line升空到位后才开始炸。translateX距离72px是外圈半径52px是内圈半径想炸得大就调这两个值。实际用下来这种纯CSS的方案在移动端表现非常稳定因为全程只有transform和opacity两种属性在变化浏览器可以走GPU合成通道。4.2 jQuery文字烟花让“新年快乐”四个字飞出去jQuery方案在合集中的存在意义和canvas完全不同它能直接把文字当烟花放。原理很简单点击时创建几个span每个span装一个字符然后利用jQuery的.animate同时改变left、top和opacity动画结束后把DOM移除。$(document).on(click, function (e) { var chars [新, 年, 快, 乐]; var colors [#ff6b6b, #ffa94d, #69db7c, #4dabf7]; chars.forEach(function (ch, i) { var $spark $(span).text(ch).css({ position: fixed, left: e.clientX, top: e.clientY, fontSize: 30px, fontWeight: bold, color: colors[i], zIndex: 9999 }).appendTo(body); var angle -Math.PI / 2 (i - 1.5) * 0.45; // 向上的扇形散开 var distance 90 i * 15; var targetLeft e.clientX Math.cos(angle) * distance; var targetTop e.clientY Math.sin(angle) * distance; $spark.animate({ left: targetLeft, top: targetTop, opacity: 0 }, 850 i * 60, function () { $(this).remove(); }); }); });这段代码里每个字符对应一个扇形方向四个字彼此错开15度左右飞出去时不会叠在一起。distance是90到135像素落点不会太近也不会飘出屏幕。animate的时长是850毫秒加索引偏移四个字不是同时消失而是依次淡出看起来更像一次有层次的溅射。最后一个细节是动画结束后的remove回调老合集里很多jQuery烟花跑完不清理DOM点几次之后body里堆积了几十个无用的span页面越来越卡这个坑后面还会展开。4.3 三种方式怎么配合一个页面完全可以一主一辅不用非选一种。我见过比较舒服的用法是整页背景交给canvas跑大粒子烟花页面正中央再来一个jQuery文字烟花点击时先弹出“新年快乐”字样背景再补一圈彩色火花。两个方案各管各的渲染层互不干扰。canvas负责氛围密度jQuery负责重点信息CSS那套则适合在不需要交互的局部区域做固定装饰。只要别让两套方案的粒子总量同时拉满页面完全扛得住。5. 让合集真正能用的5个避坑记录从粒子索引错位到低端机掉帧5.1 背景越跑越亮最后白屏一片现象页面开了一会儿后整个canvas背景从深色慢慢变成灰白最后火花全部看不清只剩一片亮色。原因这是拖尾实现的经典翻车。前面最小实现里拖尾是靠每帧用半透明背景覆盖旧画面实现的rgba(10, 14, 26, 0.22)每次只盖22%的不透明度。但如果火花颜色过于明亮、粒子数量又多残影叠加的速度会超过背景覆盖的速度画面亮度就会逐步累积。在浅色页面里这个问题尤其明显。解决深色背景方案把背景颜色的alpha降到0.15到0.25之间同时把火花颜色的亮度压低比如用hsl(色相, 80%, 60%)而不是90%亮度。如果你的页面背景是白色不要用拖尾叠加方案直接改成每帧clearRect清屏再给每个火花维护一个位置数组来画小拖尾而不是靠半透明叠加。另一种思路是定期把背景完全重绘一次比如每60帧做一次全量覆盖打断残影的累积。5.2 forEach里splice火花莫名跳变或消失现象同一朵烟花里有的火花飞到一半突然消失有的火花看起来像在闪烁跳动还有人反馈“爆炸的花火个数不稳定有时候多有时候少”。原因这是遍历数组时删除元素造成的索引错位。很多人写粒子更新时习惯用forEach在遍历过程中调用particles.splice(i, 1)可forEach的回调里拿到的index是基于原始数组的。splice删除一个元素后后续所有元素的下标都会前移循环继续走旧下标就会跳过真实的下一个粒子表现就是粒子数量变少、运动不连续。解决遍历粒子数组时使用倒序for循环从数组末尾往头遍历这样splice(i, 1)删除的元素总是在当前位置之后已经处理过的区域不会影响还没遍历的元素。这是canvas粒子系统里最基本的血泪经验凡是你在网上看到“粒子会跳”的反馈十有八九都是这里出了问题。5.3 高分屏上canvas模糊点击坐标还偏了现象同一份代码在普通屏上正常在Retina屏上画面发虚点击鼠标时烟花总会偏离手指或鼠标位置一段距离。原因canvas的width和height属性是绘图缓冲区的大小而CSS样式里的宽高是显示大小。高分屏的devicePixelRatio大于1如果不做处理浏览器会拿低分辨率的绘图缓冲去适配高分辨率的显示区域画面自然模糊。点击坐标偏位则是因为事件坐标是CSS像素而你在绘制时如果用缓冲区像素做运算两者没有对齐。解决创建画布时把物理像素尺寸设置到缓冲区CSS尺寸保持逻辑像素再对绘图上下文做一次scalevar dpr window.devicePixelRatio || 1; var W window.innerWidth, H window.innerHeight; canvas.width W * dpr; canvas.height H * dpr; canvas.style.width W px; canvas.style.height H px; ctx.scale(dpr, dpr);之后所有坐标运算都按照逻辑像素来鼠标点击的e.clientX、e.clientY可以直接使用不会再偏。5.4 纯CSS火花在低端安卓机上卡成一帧一帧现象同样的CSS烟花在iPhone上很流畅在千元安卓机上就变成幻灯片火花轨迹一卡一卡的。原因低端机的GPU合成能力弱对大量box-shadow和filter的动画处理性能很差。纯CSS烟花如果设计时用了多组box-shadow来做光晕、用了blur滤镜做模糊这些属性每帧变化都会触发大量像素填充是最耗性能的组合。解决粒子动画只使用transform和opacity两个属性它们走合成器通道不触发layout也不触发重绘。光晕效果不要用box-shadow打多层点而是用一个放大后淡出的半透明圆形div模拟冲击波这就是常说的“涟漪光圈扩散”做法视觉效果接近但性能开销小很多。另外把火花粒子数量控制在12个以内方向间距可以加大牺牲密度换流畅度。5.5 自动发射的烟花没控制总数10分钟后彻底卡死现象页面挂着不动过十分钟后再点整个页面几乎无法响应DevTools里的内存占用持续上升。原因自动播放的setInterval一直在创建新粒子如果代码里没有总量上限粒子数组会无限增长。每一次tick都要遍历所有粒子来更新和绘制数组越大每帧耗时越长最终帧率趋近于零。更隐蔽的是某些合集的canvas实现只负责画、不负责删粒子life耗尽后只是不再绘制但对象仍然留在数组里继续被遍历。解决两层保险。第一层每次launch前判断particles.length是否超过最大值超了就拒绝新粒子。第二层粒子生命周期结束时的splice不能省同时要是担心数组碎片过多可以在超过上限时直接批量截断particles.splice(0, particles.length - MAX)。这样虽然旧粒子会闪一下消失但至少页面不会崩。6. 把烟花接到你的页面上参数优化、点击交互与移动端适配6.1 一份参数速查表调出不同的“烟花性格”花火不是越大越好看有的页面需要“细碎小烟花”有的需要“饱满大花”。下面这张表是常用参数和它们的性格影响照着改就行。参数所在位置取值范围调大后的效果vy 升空速度launch()-4 到 -12升得越高适合大场景但太快会看不清弹道targetY 爆炸高度launch()距发射点 100~400炸点越高视觉越开阔别超过屏幕顶部count 火花数量explode()20~90越多越饱满超过90会在低端机掉帧speed 火花初速explode()1~6爆炸半径越大适合空旷的桌面端页面life 火花寿命explode()30~100越长拖尾越明显适合慢节奏页面背景alphatick() 的rgba0.1~0.4越小拖尾越长0.4以上几乎无拖尾MAX_PARTICLES顶层常量300~1000控制总粒子数移动端建议不超过500一版我比较推荐的“饱满型”配置是vy-10、targetY-300、count70、speed4.5、life80、alpha0.15。这一套在深色背景的博客首页上非常出片。如果是移动端H5把count降到45、speed降到3、alpha提到0.25减少粒子总量保住帧率。6.2 嵌入已有页面时三层关键设置要把canvas烟花挂到已有页面上最干净的方式是用一个独立的fixed覆盖层不要直接往body里塞canvas。覆盖层要同时设置z-index和pointer-events#fireworks-layer { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 9998; pointer-events: none; } #fireworks-layer canvas { width: 100%; height: 100%; }pointer-events: none这一行是关键。没有它铺满全屏的canvas会拦截掉页面上所有点击事件用户连按钮都点不了。设置之后鼠标事件会穿透canvas落到下层真实的页面上。同时把click监听从canvas上移到document上这样用户点页面任意位置都触发烟花又不会影响按钮、链接的正常功能。注意监听document时要用e.clientX和e.clientY来取坐标这两个值在canvas的坐标系里是通用的。6.3 页面切到后台时暂停发射回来再恢复手机浏览器进入后台后requestAnimationFrame会被自动暂停但setInterval不会。如果页面切后台五分钟再回来定时器会在这期间积累大量回调一恢复就猛炸几十朵直接卡顿。处理方式很简单监听visibilitychange事件var autoTimer null; function startAutoLaunch() { if (autoTimer) return; autoTimer setInterval(function () { if (particles.length MAX_PARTICLES) return; launch(Math.random() * W, H 5); }, 1200); } function stopAutoLaunch() { clearInterval(autoTimer); autoTimer null; } document.addEventListener(visibilitychange, function () { if (document.hidden) { stopAutoLaunch(); } else { startAutoLaunch(); } });把原来的setInterval改造成可启动、可停止的结构切后台停掉回到前台再恢复。这里还有一个顺手的小优化页面刚加载时不要立刻发射等300毫秒再开避免首屏性能竞争导致页面打开瞬间掉帧。我自己的一个教训是把这层canvas直接铺在业务页面上忘了加pointer-events: none结果上线后用户反馈“页面所有按钮都点不动”排查了半小时才发现是烟花层挡住了。后来我习惯性地把覆盖层、canvas、事件监听三层结构固定成一个模板每次做类似效果直接套用很少再翻车。这大概就是这类“代码合集”真正该沉淀下来的东西——不只把效果跑通还把边界和坑一起传下去。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑