ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

H5开红包特效从0到1:CSS3动画+Canvas粒子实战指南

H5开红包特效从0到1:CSS3动画+Canvas粒子实战指南 简介《h5开红包特效》是一份面向前端入门开发者及活动页面开发者的可直接运行示例用于解决H5互动红包功能从动画实现到交互逻辑的快速搭建问题。资源聚焦CSS3动画、JavaScript事件处理、DOM动态更新与随机金额生成等关键点配套完整html页面、base.css样式、jquery-1.10.1.min.js库以及开启前、开启后、背景等多张png/jpg切图共9个文件、压缩包仅130KB轻量易集成。已有1072人学习/下载。开发者拿到后可直接打开入口页预览默认动效也可替换图片素材、调整CSS变量或随机逻辑快速应用到电商促销、社交分享等场景。整体代码结构清晰模块化程度较好适合作为二次开发或学习H5交互动画的基础模板。 去年做跨年活动时接了一个听起来很简单、做起来全是大坑的需求H5开红包特效。一个红包图点一下撕开蹦出金额配上金光粒子用户截图分享再到朋友圈。产品经理说“就一个开红包你半天搞定吧”结果从交互节奏到移动端兼容前前后后磨了一个多星期。这篇文章我就把整套h5开红包特效从设计拆解到代码实现、再到线上排坑的经验完整梳理一遍给打算做营销互动页面的前端同学当个参考。先说清楚这个东西到底是什么。所谓H5开红包就是在手机浏览器、微信内置浏览器或者App的WebView里渲染一个可点击的红包卡片带动效拆开然后弹出金额或者祝福语的互动页面。它本身不是新技术而是几种常规前端能力的组合CSS3动画、Canvas粒子、移动端事件处理、接口数据对接。难度不大但细节极多。适合刚接触H5互动开发的中级前端也适合想从零做一个红包活动页的产品或全栈开发者了解其中的实现逻辑。1. 需求拆解别小看一个开红包的交互1.1 交互链路和用户心理开红包看起来是“点一下”实际完整的用户动线是这样的进入页面看到红包 - 被红包外观和动态提示吸引 - 点击 - 拆开过程 - 看到金额 - 分享或再次参与。每一步都对应一个明确的开发诉求。红包外观要够醒目所以需要光晕、底纹、摇摆动效点击动作要即时反馈所以触发了抖动动画和音效拆的过程要有仪式感所以设计了封口撕裂和粒子爆炸金额展示要有惊喜感所以数字滚动不能少于0.5秒还要有金光闪烁。如果你只是随便找一张红包底图加个click事件把金额alert出来那用户3秒就划走了。开红包这类互动页面技术永远不是瓶颈节奏和质感才是。这也是我后来才想明白的事情开发的第一步不是写代码而是和设计、产品对清楚“用户拆红包的0到2秒内到底看到什么”。1.2 特效选型CSS3动画加Canvas组合拳定下交互链路后要选实现方案。我当时的候选方案有四个纯CSS3动画、Canvas粒子、Lottie动效、视频序列帧。各有各的适用场景这里整理一张对比表方案实现成本包体积灵活度适用场景纯CSS3动画低极小中红包抖动、翻面、缩放、光晕呼吸Canvas粒子中极小代码自绘高拆包黄金粒子、雪花、星星Lottie动效低设计师出JSON中中复杂但固定的品牌动效视频序列帧低但要切图大低超复杂场景一般不推荐我最后选了CSS3动画配合Canvas粒子。原因很直接红包主体动效用CSS3写性能好、不用加载额外资源粒子爆炸这种不规则、大量散落的元素CSS3写起来又累又难维护Canvas几行代码就能控制每个粒子的位置、速度和颜色。两者互补正好覆盖全部特效需求。Lottie很好但需要设计师配合导出项目工期紧的话会很被动。1.3 素材准备图、音效、体积控制特效决定观感素材决定体积。红包底图能用PNG就尽量用PNG不要塞一张1920宽的大图。我当时把红包素材压到了640px宽度UI切图时特意提醒她保留透明通道避免一圈白底。音效方面拆红包的“唰”声和金币声是点睛之笔。音频文件我建议用MP3格式码率压低到64kbps以下一个音效控制在30KB以内。因为开红包页面基本都在微信里打开微信内置浏览器对自动播放音频限制很严这个问题后面单独说。总之素材这块的原则是能压缩就压缩小于1MB的整体页面加载体积是及格线。2. 红包卡片实现从静态到会摇的2.1 DOM层级结构设计我习惯把红包页面拆成四个层级背景层、红包层、遮罩层、结果层。这样分层的核心原因是避免z-index混乱尤其是金币粒子要在红包上层散开结果弹窗要在最上层层级错了就会出现“粒子跑到弹窗后面”这种尴尬问题。背景层.bg渐变背景、氛围光斑 红包层.red-packet红包主体、封口、光效、抖动动画 粒子层.particles动态创建的 canvas 画布悬浮显示 遮罩层.mask弹窗出现时的半透明黑色背景 结果层.result金额弹窗、按钮、关闭入口实际代码中红包层的DOM大概是下面这个样子div classred-packet-wrap idredPacketWrap div classred-packet div classred-packet__glow/div div classred-packet__body img src./red-packet.png alt红包 / div classred-packet__seal/div /div /div /div注意我单独放了一个__glow光效层用CSS的::before也行但单独用元素在后期做闪烁动画时可控性更强。再有就是__seal这个封口元素它是后面“撕开”动画的关键必须和红包主体分离。2.2 抖动动画编写红包在页面里不能是静止的至少要做一个轻微的摇摆或呼吸动效让用户一眼就知道“这个能点”。我用CSS3的keyframes写了两种动效组合一种是红包整体的轻微左右摇摆模拟挂在墙上被风吹动的感觉另一种是点击瞬间的大幅度抖动模拟“被用户晃动”的反馈。keyframes packet-sway { 0%, 100% { transform: rotate(-2deg); } 50% { transform: rotate(2deg); } } keyframes packet-shake { 0%, 100% { transform: translate(0, 0) rotate(0deg); } 20% { transform: translate(-6px, 2px) rotate(-6deg); } 40% { transform: translate(6px, -2px) rotate(6deg); } 60% { transform: translate(-4px, 1px) rotate(-4deg); } 80% { transform: translate(4px, -1px) rotate(4deg); } }第一次点击时我把红包的animation从packet-sway 3s ease-in-out infinite切换成packet-shake 0.6s ease-in-out感觉就像红包被用户“摇开”了。这里有个小细节切换动画时最好先加一个requestAnimationFrame延时或者强制触发一次浏览器的重排否则连续切换动画类有时会不生效。2.3 防止重复点击的锁用户手速快的时候1秒点五次红包非常常见。如果不做处理接口会被连打五次拆开动画也会重复触发视觉上直接穿帮。我的解决办法是在点击后立刻加一个“状态锁”动画完成后才解锁。let isOpening false; redPacketWrap.addEventListener(click, function () { if (isOpening) return; isOpening true; shakeAndOpen(() { // 动画完成 isOpening false; }); });除了JS状态锁还可以给红包加一个临时的pointer-events: none样式双保险。这个习惯帮我避免了很多线上事故特别是接口响应慢的时候用户狂戳按钮导致的并发问题。3. 拆开动画红包撕开那一下3.1 撕开封口的clip-path实现拆红包的“仪式感”主要在“撕开”这个动作上。最开始我做成红包整体放大消失效果平淡。后来设计提了一个思路封口像被撕开一样往两边裂开。用CSS的clip-path就能实现不用重新切图。我的实现逻辑是点击后红包主体不动封口元素translateY向上移开一段距离同时用clip-path从矩形裁切成不规则的锯齿形模拟撕裂边缘。.red-packet__seal { transition: transform 0.4s ease-in, clip-path 0.4s ease-in; clip-path: polygon(0 0, 100% 0, 100% 30%, 80% 40%, 50% 25%, 20% 35%, 0 20%); } .red-packet--opened .red-packet__seal { transform: translateY(-80px); clip-path: polygon(0 0, 100% 0, 100% 15%, 75% 35%, 50% 20%, 25% 30%, 0 15%); }这个动效是纯视觉上的“假撕裂”它不需要真实的物理模拟只要速度快、边缘不规则用户就会觉得“真的撕开了”。实测下来0.3到0.4秒的过渡时长最合适太短看不清楚太长拖沓。3.2 Canvas粒子爆炸撕开红包的瞬间要有一圈金灿灿的粒子从红包中心爆开这是整个H5最抓眼球的地方。粒子部分我用Canvas实现思路很简单在红包中心创建一个全屏Canvas生成若干粒子对象每个粒子有初始角度、速度、颜色和重力然后通过requestAnimationFrame逐帧更新位置和透明度。下面是一个精简的粒子爆炸核心代码function createParticles(canvas, x, y, count) { const ctx canvas.getContext(2d); const particles []; const colors [#ffd700, #ffaa00, #ffec80, #ffe066]; for (let i 0; i count; i) { const angle Math.random() * Math.PI * 2; const speed 4 Math.random() * 8; const size 2 Math.random() * 4; const g 0.15 Math.random() * 0.1; particles.push({ x: x, y: y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed - 2, size: size, color: colors[Math.floor(Math.random() * colors.length)], gravity: g, life: 1 }); } function update() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (let i particles.length - 1; i 0; i--) { const p particles[i]; p.vy p.gravity; p.x p.vx; p.y p.vy; p.life - 0.012; if (p.life 0) { particles.splice(i, 1); continue; } ctx.globalAlpha Math.max(p.life, 0); ctx.fillStyle p.color; ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); ctx.fill(); } if (particles.length 0) { requestAnimationFrame(update); } else { ctx.clearRect(0, 0, canvas.width, canvas.height); } } update(); }粒子数量要考虑到手机性能。我在中高端机型上跑过200到300个粒子完全没问题但低端安卓机上超过200个就会掉帧。后来我把粒子数量改成按设备动态计算navigator.hardwareConcurrency大于8就用260个小于4就降到80个。上线后低端机用户反馈明显变好。3.3 金额数字滚动粒子炸完之后弹窗里要显示金额。如果你直接innerHTML 88.88元虽然也能用但少了“钱一个个滚出来”的惊喜感。我用requestAnimationFrame做数字滚动让金额从0快速涨到目标值整个过程控制在500毫秒左右。function rollNumber(el, target, duration 500) { const startTime performance.now(); function tick(now) { const progress Math.min((now - startTime) / duration, 1); const eased progress 0.5 ? 2 * progress * progress : 1 - Math.pow(-2 * progress 2, 2) / 2; const current Math.floor(eased * target * 100) / 100; el.textContent current.toFixed(2); if (progress 1) requestAnimationFrame(tick); } requestAnimationFrame(tick); }之所以不用setInterval是因为定时器在页面切后台或滚动时会被浏览器节流动画会明显卡一下requestAnimationFrame是跟着屏幕刷新率走的天然流畅还能自动暂停释放资源。数字滚动用缓动函数处理一下前快后慢更接近真实数据增长的视觉感受。3.4 接口请求的顺序问题还有个大坑是接口顺序。很多新手是先请求接口拿到金额后再播放动画这样会导致接口慢的时候用户点了红包半天没反应体验极差。我的做法是点击红包后立即播放拆开动画同时并发发起接口请求等动画播放完如果接口还没返回就显示一个极短的加载态如果接口已经返回就直接渲染金额。这样做的核心原因是用户心理拆开红包的动作不能等结果可以等。只要拆开动作够快用户就会觉得页面流畅。还有一点要提醒开红包接口务必做幂等设计同一个红包ID不能被领取两次服务端要用用户维度加锁否则很容易被刷接口。4. 移动端适配与性能优化4.1 iOS Safari的100vh和滚动穿透开红包页面最常见的适配问题就是100vh。在iOS的Safari和微信内置浏览器里100vh的语义是“视口高度”但地址栏和底部工具条会动态变化导致页面底部被遮挡或者背景露白。我的方案是外层容器用position: fixed撑满整个屏幕而不是直接用100vh.page { position: fixed; inset: 0; overflow: hidden; }另外弹窗出现后背后的红包页面依然可以滚动这就会出现“滚动穿透”问题。解决办法是弹窗显示时给body加overflow: hidden同时在弹出层上阻止touchmove事件冒泡。resultModal.addEventListener(touchmove, function (e) { e.preventDefault(); }, { passive: false });这里我要专门提一下iOS端如果passive设成true默认preventDefault会不生效必须显式传{ passive: false }。很多同学栽在这个细节上排查了半天也不知道为什么滚动禁止不掉。4.2 微信内置浏览器的声音和返回条问题开红包往往配音效但微信内置浏览器对音频自动播放的限制非常严格用户没有交互之前HTMLAudioElement的play()会被直接拦截。我的处理方式点击红包的那一下先触发一次音频播放用户手势内可以播放然后立刻暂停预热音频等到真正需要播放金币声的时候再重新播放。这样可以绕过自动播放限制。如果你的红包页面要嵌到微信小程序里还要注意一个和热词里一样的问题小程序WebView内嵌H5时页面自带的导航栏和返回箭头会和H5里的内联元素相互干扰。最稳妥的做法是H5页面自己设计返回按钮同时在小程序的web-view配置里隐藏默认导航栏避免出现“两个返回条”叠在一起的丑态。4.3 动画性能优化开红包页面动画密集最容易出现的问题就是“低端机卡成PPT”。有几点优化经验可以分享动画属性尽量用transform和opacity避免动width、top、left这些会触发布局的属性给频繁变化的元素加will-change: transform提前告诉浏览器该元素会动走GPU合成。.red-packet, .red-packet .red-packet__glow { will-change: transform, opacity; }切记不要滥用will-change每个元素都用它反而会让浏览器崩溃。另外Canvas画布的尺寸要和显示尺寸一致不要用超大Canvas再用CSS缩下去这样GPU内存占用很高。我的做法是用window.devicePixelRatio做适配但把倍率限制在2以内避免3倍屏吞内存。4.4 低端安卓机的降级方案做到最后还要有一层降级兜底。我当时的做法是用navigator.userAgent粗略识别安卓机型如果内存小于4GB或者跑分过低就直接减少粒子数量、关闭闪光滤镜、把阴影效果去掉。CSS动画照跑Canvas粒子砍一半。这样虽然炫酷程度降低但至少不会白屏卡死。判断设备性能可以用下面这段简单代码function isLowEndDevice() { const memory navigator.deviceMemory || 8; return memory 4; }navigator.deviceMemory在安卓Chrome上支持度不错iOS上没这个API就默认走高端分支。5. 常见问题排查速查表开发过程中肯定会出现各种稀奇古怪的线上问题我这里整理一个常见问题速查表基本都是我实际遇到的问题现象根本原因解决办法红包点击没反应遮罩层z-index盖住红包检查.mask是否高于红包层调层级或让遮罩忽略点击事件动画卡顿掉帧明显动画属性引发重排/重绘改用 transform 和 opacity低端机降低粒子数量拆开动画后没有粒子Canvas尺寸为0Canvas放在隐藏容器里时宽高计算为0需在动画前重置宽高数字滚动卡顿使用了 setInterval 频繁更新文本改用 requestAnimationFrame金额弹窗出现后底部还能滚动touchmove被动监听导致 preventDefault 失效监听器改为{ passive: false }iOS上字体突然变大input/button聚焦时字号小于16px字体设置为16px以上拆红包音效播不出来微信内置浏览器自动播放限制用户手势内先play并pause预热一次接口返回慢导致红包一直转圈先请求接口再播放动画改成动画和接口并发接口慢时先展示loading排查这类问题我强烈建议在测试环境开启vConsole真机上直接看报错和网络请求比猜快得多。还有就是要多准备几台安卓低端机只拿iPhone做测试是发现不了真实用户痛点的。6. 业务集成与场景扩展6.1 封装成Vue/React组件开红包特效在业务里不是一次性页面很多活动都用得到。所以我当时把整个拆红包流程封装成了一个开箱即用的组件暴露几个配置项红包图片、拆开前文案、拆开后金额回调、是否展示粒子效果、是否需要音效。在Vue项目里用起来就像下面这样template red-packet :imageredPacketImage :amountamount :enable-particlestrue openhandleOpen finishhandleFinish / /template组件内部把状态机理顺idle - shaking - opening - showResult每个状态对应不同的DOM和CSS类。这样业务方只需要关心拿到的金额和最终的用户行为不用管动画细节。6.2 免登录跳转和企业内嵌H5的注意点红包页面通常嵌入在公众号、小程序、企业微信或钉钉飞书这类应用里。如果你在钉钉或飞书里跑H5页面免登录跳转是绕不过去的环节。我当时的做法是H5加载后先读取URL上的临时授权码请求后端换取用户身份成功后跳转回原活动地址并携带会话态整个过程要处理好iOS的Cookie写入问题否则跳转后session丢失又要重新授权。企业微信里嵌入H5时还要注意应用的可信域名配置前置URL重定向的location.href变化太多会导致授权流程被打断。我的经验是授权跳转务必放在首屏渲染之前用后端302去跳而不是前端多次location.replace这样不容易丢失Referrer和Cookie。如果你要做的是UniApp跨端H5开红包特效的思路完全一样CSS部分直接复用Canvas逻辑封装成uni.createCanvasContext即可但要注意不同端的Canvas API差异很大建议核心逻辑还是走原生H5方式再通过web-view内嵌。7. 最后再分享一点个人经验开红包特效这种需求技术上没有攻坚难点难点全在细节用户手速比你想象的快低端机比你想象的卡微信浏览器的限制比你想象的多。我的个人体会是这类互动页面的成败往往取决于性能和容错而不是特效本身。把一个500毫秒的拆开动画打磨到位比堆十个花哨动效更有用。另外一个很有用的拓展开源思路把粒子爆炸的参数数量、颜色、速度做成可视化配置面板放到团队内部给运营用。运营直接拖拽就能调“金币爆炸”的强度和光效颜色不用每次发版改代码。我当时花了一天时间做了个基础版结果不但省了自己改需求的功夫还成了团队内部用得最多的一个小工具。如果你正准备做一个H5开红包页面我的建议很简单先在纸上画清楚交互链路再写代码素材能压缩就压缩接口要做好幂等上线前多拿几台低端安卓机实测。做到这几条你就已经超过大多数临时上线的互动页面了。本文还有配套的精品资源点击获取
返回列表