ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

H5 3D雪花背景动画实战:Canvas 2D与Three.js粒子系统选型与性能调优

H5 3D雪花背景动画实战:Canvas 2D与Three.js粒子系统选型与性能调优 简介一款基于HTML5、CSS3与jQuery实现的3D雪花飘落背景动画源码适合网页前端开发者借鉴和复用可直接嵌入个人网站、活动专题页或节日氛围页面增强背景的立体感与冬日意境。压缩包共4个文件包含2个JavaScript脚本、1个HTML页面和1个CSS样式表整体仅10KBJavaScript脚本负责雪花对象的生成、坐标更新、飘落路径以及Canvas逐帧重绘CSS样式表则利用transform、animation与keyframes规则定义雪花在三维空间中的旋转、缩放和渐变下落效果代码结构清晰方便按需调整雪花数量、尺寸、速度与透明度。目前已有66人学习下载。仔细研读源码可以掌握Canvas动态绘图与requestAnimationFrame动画循环的协作方式理解CSS3 3D变换和关键帧动画的实际落地写法同时学会借助jQuery简化DOM操作与事件绑定对于想要入门Web特效或希望快速获取现成雪景动画素材的开发者而言这是一份小巧实用的参考资料。1. 当页面需要“3D雪花”时技术选型不只是引擎问题“H5大雪纷飞3D雪花背景动画”这个标题看起来像是一个特效资源包实际上涵盖了三件事H5页面如何在移动端渲染大量3D视觉粒子、如何让背景层不挡住业务操作以及打包成zip后如何快速验证。这种效果通常用在品牌活动页、倒计时首页、直播礼物氛围和企业年终首屏一旦掉帧用户对页面的第一印象会立刻变差。适合读这篇的人是前端开发、H5混合开发或独立开发者不一定需要WebGL基础但需要能在现有项目里独立改造并维护一个可改参数的雪花背景。下面给出纯Canvas 2D和Three.js两套代码再讲参数设置与落地时的坑。2. 3D雪花背景的核心原理与选型先搞懂“3D”是怎么画出来在动手写代码之前得先分清这个标题里的“3D”是哪种3D。我见到的zip包里至少有一半是用Canvas 2D仿三维视差不是WebGL空间投射。两种方案的代码结构、调参手段和兼容性差别很大但最关键的是你能看懂它才能改参数。2.1 粒子系统雪花背后的最小物理模型雪不是一个图片在屏上飘而是一组粒子。每个雪花保存自己的x、y、z坐标和速度vx、vy、vz然后在每一帧里执行两步更新位置把三维坐标投影成屏幕上的二维坐标。公式通常长这样const scale perspective / (perspective z); const screenX centerX (x - centerX) * scale; const screenY y * scale; const size baseSize * scale;逻辑说明当z增大时scale减小雪花就变小并移向观察方向z减小则靠近镜头变大。这就是最便宜的“3D感”。由于没有真实深度缓冲每一帧绘制顺序并不严格按深度排序但雪花粒子互相重叠不敏感所以视觉上不会露馅。参数说明perspective取值越大透视越平缓取值在200到600之间比较自然。z的取值范围决定雪花从远到近的跨度跨度越大视差越明显同时也意味着有时雪花会突然变大显得“冲脸”。你可以把z的变化率也当成速度的一个维度用来制造“近处下得快、远处下得慢”的视差。真正做沉浸式雪花背景不只是让一个粒子匀速下落还要控制它的亮度与雾化效果。近处雪花应更大、更亮远处应更小、更透明这比三维坐标本身更影响观感。透明度建议写成alpha baseAlpha * (z / maxZ)当z小近时透明度高z大远时偏暗。不过Canvas的globalAlpha结合粒子绘制容易重影所以更多是把透明度和颜色一起写进填充样式。2.2 渲染方案对比CSS动画、Canvas 2D、WebGL/Three.js方案粒子数上限3D程度包体成本典型适用CSS transform/keyframes3060伪3D靠缩放和透明度无极简H5背景不追求交互Canvas 2D150300伪3Dz轴缩放与视差无活动页通用性能稳定Canvas Sprite 粒子系统3001000可做3D投影带贴图贴图资源需照片级雪景Three.js / WebGL100010000真3D空间可转镜头three.min.js或gzip约150k强氛围首屏、重交互注意表格中“包体成本”不是0程序本身也有成本。前三项没有外部库依赖这是“H5大雪纷飞3D雪花背景动画.zip”这类资源包最常见的做法因为它解压之后直接双击就能看到效果不依赖“运行环境必须联网加载CDN”。如果上来就用Three.js反而会让工单群进到“为什么file://下没效果”的问题。我一般会这样选型如果项目在微信公众号内部浏览器、小程序内嵌H5或低端安卓机上运行采用纯Canvas 2D把粒子数控制在160220避免JavaScript垃圾回收抖动。如果营销页面需要割裂出“近景大雪花扫过屏幕”的冲击感再考虑引入Three.js Points。这里的取舍不是“WebGL更牛就选WebGL”而是“zip包能不能在移动端开箱即用”。2.3 为什么CSS雪景替代不了“3D雪花背景”CSS动画做雪是给每个DOM节点配置transform: translate3d和animation-delay。节点少时看着干净但30个以上在低端安卓上就开始卡顿因为每一个节点都触发独立的动画合成层更重要的是CSS动效拿不到粒子坐标后续想“风吹手滑、点击炸雪花”都很难扩展。反过来用Canvas把所有雪画在一个画布里只有画布这一个合成层粒子数量多一倍也不见得慢而且随时能读取粒子对象数组做交互。把多个z平面的粒子按照不同速度平移就是“3D背景”最核心的纵深感。当你在性能面板里看到帧率从30fps升到55fps自然明白为什么大多数zip包里是Canvas脚本而不是动画库。3. 用 Canvas 2D 写一个可直接运行的 H5 3D 雪花背景这一章给出一份无外部依赖的HTML文件保存为index.html即可打开也是很多zip包里常见的首页文件框架。3.1 最小可运行实现透视投影与循环雪池下面这串代码是完整页面不考虑加载任何库直接复制到空HTML里可运行。!DOCTYPE html html langzh-CN head meta charsetUTF-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / titleH5 3D雪花背景动画/title style html, body { margin: 0; height: 100%; overflow: hidden; background: linear-gradient(180deg, #0a0e27, #1b2040); } .snow-canvas { position: fixed; inset: 0; z-index: 0; pointer-events: none; } .page { position: relative; z-index: 1; color: #fff; text-align: center; padding-top: 28vh; font-family: system-ui; } .page h1 { font-size: 3rem; margin: 0 0 .5rem; text-shadow: 0 2px 10px rgba(0,0,0,.5); } .page p { opacity: .8; } /style /head body canvas classsnow-canvas idsnowCanvas/canvas div classpage h1大雪纷飞/h1 pH5 3D雪花背景动画/p /div script const canvas document.getElementById(snowCanvas); const ctx canvas.getContext(2d); let width, height; const cfg { count: 180, windX: 0.25, fallSpeed: 1.1, minSize: 1, maxSize: 3.5, perspective: 300, zMin: 1, zMax: 4, baseAlpha: 0.75, resetZ: 0.2 }; let snow []; function rand(min, max) { return min Math.random() * (max - min); } function createParticle(initial) { const p { x: rand(0, width), y: initial ? rand(0, height) : -4, z: rand(cfg.zMin, cfg.zMax), vx: cfg.windX rand(-0.15, 0.15), vy: cfg.fallSpeed rand(0, 0.4), size: rand(cfg.minSize, cfg.maxSize) }; return p; } function resize() { width canvas.width window.innerWidth; height canvas.height window.innerHeight; } function init() { resize(); snow Array.from({ length: cfg.count }, () createParticle(true)); window.addEventListener(resize, resize); } let rafId null; function frame() { update(); draw(); rafId requestAnimationFrame(frame); } function update() { const depth 0.003; for (let i 0; i snow.length; i) { const p snow[i]; p.x p.vx; p.y p.vy; p.z - depth; if (p.z cfg.resetZ || p.y height 6) { snow[i] createParticle(false); } if (p.x -6) p.x width 6; if (p.x width 6) p.x -6; } } function draw() { ctx.clearRect(0, 0, width, height); const centerX width / 2; for (const p of snow) { const scale cfg.perspective / (cfg.perspective p.z); const x centerX (p.x - centerX) * scale; const y p.y * scale; const alpha cfg.baseAlpha * (0.4 0.6 * (p.z - cfg.zMin) / (cfg.zMax - cfg.zMin)); ctx.fillStyle #fff; ctx.globalAlpha alpha; ctx.beginPath(); ctx.arc(x, y, p.size * scale, 0, Math.PI * 2); ctx.fill(); } ctx.globalAlpha 1; } init(); frame(); /script /body /html逻辑说明update里每一帧把p.z减小相当于雪花逐渐靠近镜头视觉上就会成为一个“从远到近压过来”的效果重置到resetZ的时候雪花又回到远处。这个“循环雪池”避免了不断创建和销毁对象也让GC不要稳定几十毫秒一次。scale决定每个雪花在屏幕上的投影位置和直径z越大scale越小投影位置也越靠近视口中线这正是中心透视与正交滚动的差别。参数说明count是粒子总数控制在120240windX加random之后形成风摆fallSpeed是基础下落速度perspective建议不要低于200否则近处雪片放得太大屏幕边缘会出现明显径向拉伸。globalAlpha需要在每帧画完后恢复为1避免下一帧继续影响背景清除。如果你把页面背景改成纯白那就把雪的填充颜色改成#eef或加阴影否则白底白字会看不出来。3.2 嵌入业务页面z-index、pointer-events 与页面手势上面代码里canvas使用position: fixed; inset: 0; z-index: 0同时pointer-events: none目的是让背景不接收任何鼠标或触摸事件。业务内容一般放在z-index: 1的容器里。如果不加pointer-events: none页面里的按钮、列表滚动很容易被canvas挡住尤其在iOS Safari下点击区域会被那一层不可见的画布吃掉。在uniapp嵌入微信公众号里的H5页面场景中你通常不会把这个canvas写进每个page而是把它挂在全局App.vue或index.html里让背景层常驻。要注意页面容器如果设了overflow: autofixed定位依然是相对viewport不受滚动影响但有些老机型对inset: 0的CSS支持不够完善可以改成left:0; top:0; width:100%; height:100%。另外一个到处踩的问题H5页面里如果有横向轮播图或tab切换雪花的vx随机偏移可能会被误认为是页面漂移。若想要风雪更贴合默认阅读动线可以把windX调低到0.15再把随机偏移控制在±0.1以内。别小看这个参数它决定了用户对首屏稳定性的第一感受。3.3 让“3D感”更强分组景深、模糊与近景大雪花纯z缩放已经能看出空间感但为了让视觉效果更像3D场景我一般会把粒子分成两组远场景z大于2.5和近场景z小于等于2.5。近景组里额外加几个大粒子半径是普通雪花的2到3倍下落速度也加快一些远景组则降低透明度和半径并在draw里用ctx.filter blur(1px)做一次轻模糊。注意ctx.filter在移动端部分浏览器上开销较大不要每帧对全部雪花开滤镜只对远景组开启就行。如果你需要雪花有真实冰晶的棱角不要用圆形ctx.arc可以改用生成一个六角星形顶点数组然后按同一路径填充。但六边形消耗比圆高粒子数大于150时建议用圆形加径向渐变模拟冰晶的高光。很多人把“3D雪花”误解成“每个雪花必须有六角形状”实际上3D感来自深度和运动视差而不是贴图是否逼真。4. 用 Three.js 实现真正的 H5 3D 雪花场景可选增强当纯2D投影不能满足“需要镜头转动”的设计时我们再上Three.js。这一章专注用Points实现大量雪粒子的3D渲染而不是把每片雪花建模成Mesh那样性能会立刻崩。4.1 为什么选 Points点精灵而不是 MeshThree.js里渲染1000个BoxGeometry也会让draw call破千移动端很难吃消。Point是GPU粒子的最简入口所有雪花只占一个draw call每个粒子通过Position坐标和尺寸来控制外观。配合BufferGeometry可以直接在CPU里挪动坐标再把needsUpdate置为true即可。如果你的雪花还需要不同的大小/透明度可以用PointsMaterial的单一size或者用ShaderMaterial对gl_PointSize和varying alpha做更细粒度的控制后者更专业但调试成本也高。这一章给出的方案把Three.js构建成离线本地文件放进zip包避免外链CDN在活动当天挂掉。4.2 离线可用的 Three.js 雪花场景最小代码这个示例假设你已经在assets/js/three.min.js放下了Three.js发行版。不需要外部纹理图雪花用CanvasTexture动态生成。!DOCTYPE html html langzh-CN head meta charsetUTF-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / titleH5 3D雪花背景 - Three.js/title style body { margin: 0; overflow: hidden; background: #0a0e27; } canvas { display: block; } /style /head body script src./assets/js/three.min.js/script script const scene new THREE.Scene(); const camera new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 100); camera.position.set(0, 0, 5.2); const renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(innerWidth, innerHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); document.body.appendChild(renderer.domElement); const COUNT 1400; const positions new Float32Array(COUNT * 3); for (let i 0; i COUNT; i) { positions[i * 3] (Math.random() - 0.5) * 8; positions[i * 3 1] (Math.random() - 0.5) * 8; positions[i * 3 2] (Math.random() - 0.5) * 4; } const geometry new THREE.BufferGeometry(); geometry.setAttribute(position, new THREE.BufferAttribute(positions, 3)); function createSnowTexture() { const c document.createElement(canvas); c.width 64; c.height 64; const ctx c.getContext(2d); const grad ctx.createRadialGradient(32, 32, 0, 32, 32, 32); grad.addColorStop(0, rgba(255,255,255,1)); grad.addColorStop(0.3, rgba(255,255,255,0.85)); grad.addColorStop(1, rgba(255,255,255,0)); ctx.fillStyle grad; ctx.fillRect(0, 0, 64, 64); return new THREE.CanvasTexture(c); } const material new THREE.PointsMaterial({ color: 0xffffff, size: 0.12, map: createSnowTexture(), transparent: true, depthWrite: false, alphaTest: 0.01, }); const points new THREE.Points(geometry, material); scene.add(points); let last performance.now(); function animate(now) { requestAnimationFrame(animate); const delta Math.min((now - last) / 16.67, 3); last now; const pos geometry.attributes.position.array; for (let i 0; i COUNT; i) { pos[i * 3 1] - 0.008 * delta; pos[i * 3] Math.sin(now * 0.001 i) * 0.0008; if (pos[i * 3 1] -4) pos[i * 3 1] 4; } geometry.attributes.position.needsUpdate true; points.rotation.y Math.sin(now * 0.0001) * 0.03; renderer.render(scene, camera); } requestAnimationFrame(animate); window.addEventListener(resize, () { camera.aspect innerWidth / innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth, innerHeight); }); /script /body /html逻辑说明粒子位置围绕原点分布Y轴负方向为下x轴加入sin做横向摆动当粒子跌破-4就重置到4形成一个无限循环。整体旋转points.rotation.y在±0.03弧度之间摆动这会给静止背景增加非常轻微的镜头感但也意味着你需要在业务页上注意方向错觉活动页有箭头类导航时建议关掉rotation。参数说明size是Three.js世界单位中的点大小会受相机距离影响0.12约在5.2相机距离下显示为3-4像素COUNT 1400在多数中端机型可流畅如果打开后掉帧先调小到800而不是去调size。depthWrite: false避免雪花互相遮挡导致半透明排序问题alphaTest: 0.01去掉边缘透明度较低的死像素。如果你用的是真3D场景建议把sizeAttenuation保留默认true否则距离远近不会改变粒子大小3D感反而消失。4.3 从“背景”到“交互”鼠标与触摸影响雪场在3D背景上增加交互是让整个首屏不显得像静态壁纸的关键。常见做法是监听pointermove把归一化坐标告诉摄像机让镜头围绕原点小幅旋转而不是动粒子自身let pointerX 0, pointerY 0; document.addEventListener(pointermove, (e) { pointerX (e.clientX / innerWidth) * 2 - 1; pointerY (e.clientY / innerHeight) * 2 - 1; }); // animate函数里更新相机位置 const targetX pointerX * 0.6; const targetY -pointerY * 0.4; camera.position.x (targetX - camera.position.x) * 0.03; camera.position.y (targetY - camera.position.y) * 0.03; camera.lookAt(scene.position);这段逻辑说明使用指数衰减让摄像机平滑朝目标移动0.03是阻尼系数系数越小跟手越柔和。pointermove在移动端触发的频次低于鼠标但在微信浏览器里够用要注意不要在touchend后继续让镜头产生偏移否则用户点击完按钮会发现背景还在缓慢旋转观感会有点晕。除了镜头交互也可以在点击位置附近生成一圈加快下落的大雪花这种额外粒子要单独维护一个临时数组用setTimeout在1500ms后移除。5. 参数验证与 zip 包上线让雪花动画不在线上掉链子把你写好的代码压缩成zip之前下面这些事会直接影响线上效果。5.1 目录结构与本地服务常见的zip包结构一般是这样snow-background/ ├── index.html ├── assets/ │ ├── js/ │ │ ├── three.min.js │ │ └── main.js │ └── images/ │ └── snow.png ├── README.md建议开发时就把main.js拆开而不是全写死在index.html因为活动页通常需要配合A/B测试或者按环境注入配置。压缩时要避免套一层外层同名目录否则解压后路径容易变成双层部署时还要再拖一层。本地验证不要双击打开index.html因为file://协议下Canvas没问题但Three.js版里纹理和外部脚本会被拦。先进项目目录开一个静态服务cd snow-background python3 -m http.server 8080然后用手机和电脑分别访问http://localhost:8080。线上注意资源请求是否404、WebGL是否被禁用。Android微信里WebGL大概率可用但部分WebView开发者选项会关闭硬件加速需要做一次降级判断WebGLRenderingContext不存在时就回退到第3章的Canvas 2D版。5.2 性能调参与后台暂停调参上最关键的三个开关是粒子数量、设备像素比和页面隐藏时的暂停。把renderer.setPixelRatio(Math.min(devicePixelRatio, 2))加上否则iPhone的高倍渲染会直接把GPU塞满页面隐藏时也应及时暂停动画避免微信后台常驻掉电let running true; document.addEventListener(visibilitychange, () { if (document.hidden) { running false; cancelAnimationFrame(rafId); } else { running true; rafId requestAnimationFrame(frame); } });逻辑说明这个监听器在页面切到后台时取消下一帧切回来重新发起requestAnimationFrame。如果你在Three.js版里使用把回调里的frame换成animate即可。5.3 上线前检查上线前我用三个指标自检第一Chrome Rendering面板打开“Paint flashing”确认每次刷新只重绘画布区域第二Performance面板记录5秒拖动页面观察FPS是否出现断崖第三在微信开发者工具里模拟中低端机型把count从180降到120再跑一次记录首屏加载耗时。三个指标都通过这套3D雪花背景参数就可以随业务一起发布。本文还有配套的精品资源点击获取
返回列表