用HTML+Canvas打造2026马年动态烟花祝福页(纯前端零依赖)
还有差不多一个月就到2026年春节了每年这时候微信里全是复制粘贴的祝福文案看多了真的分不清是谁发的。去年我换了个方式拜年——用HTML写一个动态烟花新年祝福页面单文件发过去点开就是满天烟花家里长辈都觉得挺新鲜。今年赶上丙午马年我把整个页面重做了一版烟花配色、祝福语、视觉细节都按马年调过顺便把完整实现思路和源码整理出来。这篇文章适合三类人一是想学Canvas动画但一直没找到合适练手项目的初学者二是想给家人、客户、公司群做点不一样拜年页面的运营或开发者三是单纯想拿现成源码改一改就用的朋友。文章里不会出现任何框架和依赖全部是浏览器原生HTML、CSS、JavaScript保存成一个.html文件就能跑。1. 为什么我坚持用单文件HTML做祝福页四类方案横向对比1.1 短视频模板、小程序、PPT祝福页的问题先说说我为什么不用更省事的方案。每逢春节短视频平台都会上线一堆新年模板选个背景音乐、套个模版就能生成视频。问题是这种东西太容易撞车你发出去亲戚群里已经有三个同款了完全没有这是专门给我做的那种感觉。小程序祝福页也一样开发门槛高是一方面更重要的是分享链路很别扭——点开小程序会有一个加载过程在微信里还会被各种请使用完整服务的提示打断。长辈们对小程序的第一反应往往不是好漂亮而是这是什么怎么又要授权PPT转成视频或者图片长图也做过但那种方式是静态的烟花不会动少了最关键的活的感觉。而且长图一旦压缩文字可能糊掉字号小了在手机上根本看不清。1.2 单文件HTML到底赢在哪里HTML页面方案最核心的优势是零依赖、零门槛、平台无关。不需要安装任何软件一个记事本就能改代码不需要服务器双击本地文件就能打开不需要上传App Store或过审核压缩包发过去、链接甩过去都能看视觉效果的上限极高Canvas动画能做成什么样完全取决于你的想象力。实际使用中我把这个页面压缩包通过微信文件传助手发到家庭群点开即是全屏烟花没有加载条、没有授权弹窗连手机自带浏览器都能直接播放。对长辈来说这就是打开就有的体验比任何花哨的载体都可靠。1.3 一个祝福页面的三块拼图这类页面技术上其实就三件事HTML结构负责承载页面上的文字、按钮、Canvas画布CSS样式负责夜空渐变背景、艺术字、印章等视觉元素JavaScript逻辑负责烟花的生成、运动、爆炸、消散以及文案交互。把这三点理清楚再往里填代码就不乱了。下面要讲的烟花效果全部集中在JavaScript和Canvas画布上。2. 烟花动效的三个核心机制粒子、火箭与半透明拖尾2.1 一个烟花的完整生命周期很多人一想到烟花效果就开始搜花哨的库其实拆开看它就是一个再典型不过的物理模拟问题。现实里烟花升空到炸开的过程拆成代码大概是这样的一枚火箭从屏幕底部往上飞拖着一条亮线火箭飞到指定高度后触发爆炸爆炸瞬间在爆点周围生成几十上百个粒子每个粒子拥有随机的方向和速度粒子飞出后受到重力影响速度逐渐衰减同时自身慢慢变淡粒子完全透明后从数组移除。只要把这五个环节用代码表达出来烟花效果就已经有了九成。剩下的氛围感全靠拖尾和颜色调得对不对。2.2 粒子整个效果的最小原子单位粒子系统是这个页面最核心的部分。我不想把概念讲得太玄你只要知道一个粒子就是一个会移动的小圆点它身上带着位置、速度、颜色、透明度和生命周期。function Particle(x, y, color, vx, vy) { this.x x; this.y y; this.vx vx; this.vy vy; this.color color; this.alpha 1; // 不透明度从1开始逐渐变淡 this.decay 0.008 Math.random() * 0.01; // 每次更新衰减多少 this.gravity 0.045; // 重力加速度 } Particle.prototype.update function () { this.vx * 0.985; // 空气阻力 this.vy * 0.985; this.vy this.gravity; // 重力让粒子向下拐弯 this.x this.vx; this.y this.vy; this.alpha - this.decay; return this.alpha 0.02; };这里有几个数值是我反复调出来的说一下心得速度衰减0.985代表空气阻力。如果不加粒子会一直匀速直线飞出去炸开后像爆炸的瓜子壳完全不像烟花。乘上0.985之后粒子会慢慢减速停在半空再下坠才有一朵烟花缓缓绽放的感觉。重力0.045决定了烟花的形态。重力太大粒子刚炸开就被拽向地面形成一束垂柳重力太小粒子飘在半空半天不落节奏拖沓。0.045是我在1920x1080屏幕上测试比较舒服的数值你可以按自己屏幕尺寸微调。衰减0.008~0.018之间随机目的是让粒子寿命长短不一。如果所有粒子同时消失爆炸后会出现一个明显的断崖很假。随机衰减能让粒子陆陆续续灭掉更接近真实烟火。理解了这个最小单元你就能看懂后面所有效果是怎么堆出来的。2.3 火箭先上升再绽放粒子负责爆炸后的花样火箭负责把粒子送到空中。火箭的逻辑不复杂但要提前想清楚它什么时候爆炸以什么角度飞我的做法是在创建火箭时就算好一个目标高度然后让它带一点随机的水平偏转往上飞。这个水平偏转非常重要如果所有火箭都竖直向上烟花放出来全在屏幕中轴线上画面会很死板加一个Math.random() - 0.5的偏转之后火箭会像真正的烟花一样在空中形成高低错落的爆点。function Rocket() { this.x Math.random() * window.innerWidth; this.y window.innerHeight 10; var targetY window.innerHeight * (0.15 Math.random() * 0.35); var angle -Math.PI / 2 (Math.random() - 0.5) * 0.25; var speed 6 Math.random() * 4; this.vx Math.cos(angle) * speed; this.vy Math.sin(angle) * speed; this.dist Math.abs(window.innerHeight - targetY); this.travel 0; this.trail []; }天空高度为什么要限定在屏幕的15%~50%这个区间因为太低了爆炸会被中央的祝福文字挡住太高了粒子炸开后有三分之一会飘出屏幕顶部浪费性能。把爆点控制在画面中上部是最稳妥的视觉黄金区。2.4 拖尾用半透明覆盖骗过眼睛这是最容易被忽略、但对效果影响最大的地方。如果你用clearRect()把画布每帧清空你会看到烟花是一颗一颗寂寞地闪没有任何拖尾。因为烟花要在空中留下轨迹就需要让上一帧的画面残留一点点。实现方式非常取巧每帧绘制前不用clearRect清空画布而是用一层半透明深蓝色蒙版盖上去。这样旧的画面会变暗一点点但不会立即消失新画的火星则保持明亮连续十几帧之后烟花身后就会留下一道从亮到暗的渐隐尾巴。ctx.fillStyle rgba(8, 12, 35, 0.16); ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);这个0.16就是拖尾长度的开关。数值越大蒙版越不透明旧画面消失越快拖尾越短数值越小旧画面残留越久拖尾越长。我试过0.08拖尾拉得很长但烟花炸开后轨迹太多画面显得脏试过0.3拖尾几乎看不见又回到了孤单闪光弹的效果。0.16是我在深蓝色夜空中最满意的平衡点。2.5 动画循环与发射节奏所有粒子运动都在requestAnimationFrame()的回调里完成这是浏览器提供的动画专用API会跟随屏幕刷新率执行比setInterval准时得多也不会在标签页切到后台时继续消耗资源。发射节奏也要控制一下。如果每帧都发射画面上会同时存在无数枚火箭反而看不清每一朵烟花的细节。我用时间戳做了节流if (time - lastLaunch 480 rockets.length 4) { lastLaunch time; rockets.push(new Rocket()); }意思是每480毫秒最多发射一枚火箭且同时在场火箭不超过4枚。这样烟花会错开节奏一朵刚散尽、另一朵正好炸开视觉上节奏感最好。3. 马年元素怎么融进页面丙午配色、文案与印章设计3.1 先把2026年这个细节搞对做马年专属页面第一件事不是写代码是先确认年份信息。2026年是农历丙午年生肖属马春节是2月17日。所以在页面里我写了丙午马年而不是笼统的2026马年这种细节虽然不会被人刻意注意但懂的人一眼就能感觉出页面是用心做的。想加倒计时的话可以用new Date(2026-02-17T00:00:00)减去当前时间在页面底部显示距离丙午马年还有多少天。今年我把这个倒计时放在了落地页加载前的黑屏阶段体验上更有仪式感。3.2 三色定调夜空、鎏金与中国红烟花页面的背景我用了放射状渐变的深蓝色而不是纯黑。html, body { background: radial-gradient(ellipse at 50% 20%, #1a2350 0%, #0a0e25 65%, #05081a 100%); }这个渐变模拟了真实夜空中城市上方微微泛光、高空接近纯黑的效果比平铺的纯#000更有层次。而且烟花在深蓝色背景上爆炸时暖色粒子会显得更透亮。主文字选了鎏金色用CSS渐变文字实现.year { background: linear-gradient(180deg, #fff2c0 0%, #f9c74f 45%, #d9890f 100%); -webkit-background-clip: text; background-clip: text; color: transparent; }金色带一点从上到下的明暗变化会让人联想到金属的反光。副标题和祝福语用偏亮的杏黄或浅金色整体氛围就是中国节日里最经典的红金配但我把大面积红色留给了远处的印章避免整个页面变成一个大红灯笼反而没那么高级。3.3 版式层级年份 主标题 标语 印章页面文字做了四层视觉层级从上到下依次是年份2026超大字号是一眼扫过去最先被看到的信息主标题新春快乐比年份稍小但字形更粗、更厚重副标题丙午马年 · 辞旧迎新小字拉出间距补充年份信息祝福语胶囊一个圆角边框内的短句带一点交互感。这种大中小三级排布在视觉上非常稳。你要避免的是所有文字都一个字号、一个颜色那样页面会显得像一张摊开的报纸没有重心。3.4 马年祝福文案库与点击切换马年能用的祝福语其实很多我挑了六个朗朗上口、不落俗套的马到成功、龙马精神、一马当先、骏业日新、万马奔腾、策马扬鞭。做成点击切换的交互很简单var mottos [马到成功, 龙马精神, 一马当先, 骏业日新, 万马奔腾, 策马扬鞭]; var mottoIndex 0; mottoEl.addEventListener(click, function () { mottoIndex (mottoIndex 1) % mottos.length; mottoEl.textContent mottos[mottoIndex]; });彩蛋感就在这里——对方点一下祝福语就换一个像在拆小盲盒。我把这个交互提示直接写在了页面底部点击祝福语切换内容。3.5 印章在角落盖一个马字要让页面更有马年记忆点除了文字我加了一个红色圆形印章里面写一个大大的马字。用CSS就能做不需要图片.stamp { position: fixed; z-index: 2; top: 30px; right: 30px; width: 74px; height: 74px; border-radius: 50%; background: rgba(190, 30, 40, 0.85); color: #fff5e0; display: flex; align-items: center; justify-content: center; font-size: 40px; transform: rotate(12deg); }旋转12度是关键印章如果端端正正摆在角落会显得很死板稍微歪一点才有啪一声盖上去的鲜活感。这里顺便说一句如果你想要一个更复杂的马形剪影不建议在Canvas里手绘路径直接去免费图标站找一张CC0授权的马剪影SVG内联进HTML就行效果远比自己画的高好几个档次。4. 完整源码一个可运行的2026马年烟花祝福页4.1 文件结构与使用方法整个祝福页只有一个index.html文件CSS写进styleJavaScript写进script没有任何外部文件。这意味着你可以把它存到U盘、发到微信群、传到任何静态托管平台上双击就能运行。使用步骤新建一个文本文件重命名为index.html把下面完整代码粘贴进去并保存双击打开即可看到全屏烟花和祝福文字。4.2 完整源码!DOCTYPE html html langzh-cn head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title2026丙午马年 · 新春快乐/title meta namedescription content2026马年专属动态烟花祝福页纯HTML/CSS/JavaScript实现打开即看可分享给家人朋友。 style * { margin: 0; padding: 0; box-sizing: border-box; } html, body { width: 100%; height: 100%; overflow: hidden; background: radial-gradient(ellipse at 50% 20%, #1a2350 0%, #0a0e25 65%, #05081a 100%); font-family: STKaiti, KaiTi, Microsoft YaHei, serif; } canvas { position: fixed; inset: 0; width: 100%; height: 100%; z-index: 1; pointer-events: none; } #greeting { position: fixed; z-index: 2; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; color: #f7e3af; width: 100%; padding: 0 16px; user-select: none; text-shadow: 0 0 20px rgba(255, 200, 80, 0.35); } .year { font-size: clamp(46px, 12vw, 130px); font-weight: bold; letter-spacing: 0.12em; background: linear-gradient(180deg, #fff2c0 0%, #f9c74f 45%, #d9890f 100%); -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent; filter: drop-shadow(0 4px 18px rgba(249, 199, 79, 0.35)); line-height: 1.15; } .main-title { margin-top: 8px; font-size: clamp(38px, 8vw, 96px); font-weight: bold; letter-spacing: 0.15em; color: #ffd97a; text-shadow: 0 0 30px rgba(255, 160, 40, 0.45), 0 0 60px rgba(255, 120, 30, 0.2); line-height: 1.25; } .sub-title { margin-top: 14px; font-size: clamp(14px, 3.2vw, 26px); letter-spacing: 0.3em; color: rgba(255, 241, 214, 0.85); } .motto { margin-top: 20px; display: inline-block; padding: 8px 24px; border: 1px solid rgba(249, 199, 79, 0.55); border-radius: 999px; color: #ffd97a; font-size: clamp(16px, 3.4vw, 28px); letter-spacing: 0.35em; cursor: pointer; transition: all 0.3s ease; background: rgba(20, 24, 50, 0.25); } .motto:hover { background: rgba(249, 199, 79, 0.12); box-shadow: 0 0 24px rgba(249, 199, 79, 0.25); } .tip { position: fixed; z-index: 2; bottom: 22px; left: 0; right: 0; text-align: center; color: rgba(255, 255, 255, 0.45); font-size: 13px; letter-spacing: 0.1em; } .stamp { position: fixed; z-index: 2; top: 30px; right: 30px; width: 74px; height: 74px; border-radius: 50%; background: rgba(190, 30, 40, 0.85); color: #fff5e0; display: flex; align-items: center; justify-content: center; font-size: 40px; font-family: STKaiti, KaiTi, serif; box-shadow: 0 0 30px rgba(190, 30, 40, 0.5); transform: rotate(12deg); } /style /head body canvas idfireworksCanvas/canvas div idgreeting div classyear2026/div div classmain-title新春快乐/div div classsub-title丙午马年 · 辞旧迎新/div div classmotto idmotto马到成功/div /div div classstamp马/div div classtip点击祝福语切换内容 · 建议手机/电脑浏览器打开/div script (function () { var canvas document.getElementById(fireworksCanvas); var ctx canvas.getContext(2d); var rockets []; var particles []; var maxParticles 400; var lastLaunch 0; function resize() { var dpr Math.min(window.devicePixelRatio || 1, 2); canvas.width window.innerWidth * dpr; canvas.height window.innerHeight * dpr; canvas.style.width window.innerWidth px; canvas.style.height window.innerHeight px; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); } window.addEventListener(resize, resize); resize(); function randomColor() { var hues [0, 20, 45, 50, 320, 340, 360]; var h hues[Math.floor(Math.random() * hues.length)]; return hsl( h , 90%, 68%); } function Rocket() { this.x Math.random() * window.innerWidth; this.y window.innerHeight 10; var targetY window.innerHeight * (0.15 Math.random() * 0.35); var angle -Math.PI / 2 (Math.random() - 0.5) * 0.25; var speed 6 Math.random() * 4; this.vx Math.cos(angle) * speed; this.vy Math.sin(angle) * speed; this.dist Math.abs(window.innerHeight - targetY); this.travel 0; this.trail []; } Rocket.prototype.update function () { this.trail.push({ x: this.x, y: this.y, life: 1 }); if (this.trail.length 6) this.trail.shift(); for (var i 0; i this.trail.length; i) { this.trail[i].life - 0.16; } this.x this.vx; this.y this.vy; this.travel Math.abs(this.vy); if (this.travel this.dist || this.y window.innerHeight * 0.1) { explode(this.x, this.y); return false; } return true; }; Rocket.prototype.draw function () { for (var i 0; i this.trail.length; i) { var t this.trail[i]; ctx.globalAlpha t.life * 0.8; ctx.fillStyle #ffffff; ctx.beginPath(); ctx.arc(t.x, t.y, ((i 1) / this.trail.length) * 2.5, 0, Math.PI * 2); ctx.fill(); } ctx.globalAlpha 1; ctx.fillStyle #ffffff; ctx.beginPath(); ctx.arc(this.x, this.y, 2.5, 0, Math.PI * 2); ctx.fill(); }; function explode(x, y) { var color randomColor(); var count 50 Math.floor(Math.random() * 30); for (var i 0; i count; i) { var angle (Math.PI * 2 * i) / count Math.random() * 0.2; var speed 1.5 Math.random() * 3.5; particles.push(new Particle(x, y, color, Math.cos(angle) * speed, Math.sin(angle) * speed)); } } function Particle(x, y, color, vx, vy) { this.x x; this.y y; this.vx vx; this.vy vy; this.color color; this.alpha 1; this.decay 0.008 Math.random() * 0.01; this.gravity 0.045; } Particle.prototype.update function () { this.vx * 0.985; this.vy * 0.985; this.vy this.gravity; this.x this.vx; this.y this.vy; this.alpha - this.decay; return this.alpha 0.02; }; Particle.prototype.draw function () { ctx.globalAlpha this.alpha; ctx.fillStyle this.color; ctx.beginPath(); ctx.arc(this.x, this.y, 1.8, 0, Math.PI * 2); ctx.fill(); }; function tick(time) { requestAnimationFrame(tick); ctx.fillStyle rgba(8, 12, 35, 0.16); ctx.fillRect(0, 0, window.innerWidth, window.innerHeight); if (time - lastLaunch 480 rockets.length 4) { lastLaunch time; rockets.push(new Rocket()); } for (var i rockets.length - 1; i 0; i--) { if (rockets[i].update()) { rockets[i].draw(); } else { rockets.splice(i, 1); } } if (particles.length maxParticles) { particles.splice(0, particles.length - maxParticles); } for (var j particles.length - 1; j 0; j--) { if (particles[j].update()) { particles[j].draw(); } else { particles.splice(j, 1); } } } var mottos [马到成功, 龙马精神, 一马当先, 骏业日新, 万马奔腾, 策马扬鞭]; var mottoIndex 0; var mottoEl document.getElementById(motto); mottoEl.addEventListener(click, function () { mottoIndex (mottoIndex 1) % mottos.length; mottoEl.textContent mottos[mottoIndex]; }); requestAnimationFrame(tick); })(); /script /body /html4.3 三处最值得细读的代码第一处是resize()里的DPR处理var dpr Math.min(window.devicePixelRatio || 1, 2); canvas.width window.innerWidth * dpr; canvas.height window.innerHeight * dpr; ctx.setTransform(dpr, 0, 0, dpr, 0, 0);这里解决的是高DPI屏幕模糊问题。iPhone和很多安卓旗舰机的物理分辨率是CSS像素的两倍甚至三倍如果不把Canvas画布按倍率放大画出来的烟花边缘会发糊。Math.min(..., 2)是为了限制放大倍数因为3倍DPI下Canvas像素数会暴涨到9倍普通手机渲染起来压力很大画质提升却几乎看不出区别。第二处是tick()里的半透明蒙版前面已经详细讲过。你运行完代码后可以试着把0.16改成0.05和0.3各看一遍立刻就能理解拖尾是怎么来的。第三处是particles.splice(0, particles.length - maxParticles)这个剪枝操作。当粒子数量超过400时直接从数组头部删掉多出来的部分——这相当于淘汰最老的粒子。它保证了无论烟花频率多高、屏幕多大页面都只会为最多400个粒子计算坐标和绘制稳稳控制住性能上限。4.4 改成你的配色和文案想二次创作的朋友重点改这几处就行randomColor()函数里的hues数组决定了烟花颜色。想改成国旗红金色主题就保留0、20、45、50这几个色相想改成马年更典雅的金银配色可以把色相集中在40~50之间饱和度降低.year和.main-title里的background: linear-gradient(...)和color决定文字颜色mottos数组直接换成你想要的祝福语比如送给客户可以写骏业日新马不停蹄副标题丙午马年 · 辞旧迎新也可以改成一路驰骋·万事顺遂之类的风格。5. 实测定调高DPI、移动端掉帧与微信内置浏览器的处理5.1 高DPI模糊问题第一次写完这个页面时我在一台Windows电脑上用Chrome打开一切正常。但发到iPhone上一看烟花的边缘明显发虚像是蒙了一层雾。原因就是前面说的devicePixelRatio。这个问题在桌面端不常见因为大多数桌面显示器DPR是1但在手机上几乎是必现的。所以resize()里那段DPR处理不是锦上添花而是移动端可用的前提。我把DPR上限限制在2也是为了让中低端安卓机不至于因为画布太大而明显掉帧。5.2 移动端掉帧与粒子剪枝在两千元档安卓机的微信内置浏览器里打开时如果同时有3~4枚火箭爆炸粒子数量一度能到800以上帧率会明显下降。画面表现就是烟花一顿一顿地往外蹦。两个手段解决粒子上限maxParticles 400超过就直接剪枝火箭发射节流间隔从400ms调大到480ms降低同屏火箭数量。如果还是觉得卡可以再把粒子单次爆炸数量从50 Math.random() * 30下调到35 Math.random() * 20。牺牲一点炸得密的爽感换来的整晚稳定运行是值得的。5.3 微信内置浏览器的兼容策略分享到微信的场景绕不开内置浏览器。以前老版本微信的X5内核确实对ES6支持不好所以我在这份源码里刻意采用了ES5语法——var、构造函数、prototype、普通function没有用const/let、箭头函数和class。这样做的好处是哪怕对方是台很老的低端手机只要浏览器不是古董页面都能正常跑。如果你打算把代码升级成更现代的写法比如用ES6的class建议给目标用户加一层Babel转译或者明确知道接收方设备比较新。5.4 参数调优参考表参数位置我的取值效果重力gravityParticle.update0.045决定烟花炸开后下坠速度阻力系数Particle.update0.985决定粒子减速快慢与飞行距离衰减decayParticle构造0.008~0.018随机决定粒子可见时间与渐隐速度蒙版透明度tick填充色0.16决定拖尾长度越大越短发射间隔tick条件480ms决定烟花出现的密度同屏火箭上限tick条件4防止舞台过于拥挤粒子总数上限tick剪枝400移动端保帧率的红线单次爆炸粒子数explode50~80决定单朵烟花的饱满度这张表是我调参过程中沉淀下来的数值都不是拍脑袋定的每一档都对应一个肉眼可见的视觉差异。建议大家改的时候一次只改一个参数观察完再改下一个这样你才能知道哪个值起什么作用。6. 把祝福送出去本地发送、免费托管与分享彩蛋6.1 直接发送文件的姿势最省事的方式是把index.html放进一个文件夹压缩成zip通过微信文件传助手或邮件发给对方。对方解压后双击就能打开。 有一点要注意文件名别带特殊符号和空格尽量用2026-mahun.html或新年祝福.html这种简单名字否则在部分手机上解压后可能无法直接打开。如果你是发给不太会用电脑的长辈压缩包可能有点门槛。更稳妥的方式是发一个在线链接见下一节。6.2 免费托管与分享链接把HTML页面变成网址主要用两种方式。GitHub Pages注册登录后新建一个仓库把index.html拖进去然后在仓库设置里开启Pages功能等两分钟就会得到一个形如https://你的用户名.github.io/仓库名/的链接。这个链接对接收方非常友好点开就是完整页面。Vercel如果不熟悉Git直接把文件夹拖入Vercel的Web面板也能部署流程更图形化。免费版足够个人使用。拿到链接后再用任意二维码工具生成二维码放到贺卡、邮件签名或者拜年海报里对方扫码就能打开。6.3 分享预览优化链接在微信里的预览效果取决于页面head里的几个标签。我这份源码里已经写好了title2026丙午马年 · 新春快乐/title meta namedescription content2026马年专属动态烟花祝福页纯HTML/CSS/JavaScript实现打开即看可分享给家人朋友。再想精益求精可以加一个1200x630左右尺寸的预览图片并补充Open Graph标签meta propertyog:title content2026丙午马年 · 新春快乐 meta propertyog:description content点开看2026马年专属烟花祝福 meta propertyog:image contenthttps://你的域名/preview.jpg这样链接在微信和多数社交平台里会显示成带封面图的卡片比光秃秃的文字链更有质感。6.4 再加一点互动的小脑洞页面跑通之后还可以继续往里面加东西群名定制版把mottos数组换成朋友群里的黑话或者每个人的名字和一句祝福点击轮播时像是在点名倒计时彩蛋通过new Date(2026-02-17T00:00:00) - Date.now()算出距离春节还有多少天显示在页面底部音效加持用AudioContext在烟花爆炸时合成一段白噪音短音效不用引入任何音频文件代码量不大效果却很喜庆生日/教师节通用版把mottos换成生日快乐感谢恩师年份位置改成对方年龄或9.10这套模板就能复用到其他节日。有一点要提醒如果加音乐建议用无版权音乐资源或者自己的音频文件不要直接外链其他网站的版权音源否则分享出去很容易被处理。做完这个页面之后我最大的感受是调参比写代码更花时间。写逻辑也许一个晚上就够了但为了让烟花节奏、拖尾长短、文字发光程度都刚刚好我前前后后改了十几版数值。如果你也想做一个类似的页面别指望一次写完就完美先让一个粒子动起来再加一个爆炸再加火箭、加文字、加交互每一步都能肉眼看到变化最后自然就越改越有感觉。这份源码我特意保留了最稳定的参数配置你可以直接拿去用也可以像我一样一点点改出属于自己的版本。