用 canvas 实现高性能雪花动画需控制数量(150–300个)、适配设备像素比(缩放 canvas 并调用 ctx.scale)、优化随机参数分布、监听 visibilitychange 暂停后台动画,并在 resize 时重置 canvas 尺寸和缩放。
canvas 实现高性能雪花动画,别碰 div + CSS animation
纯 CSS 做几百个 div 模拟雪花,页面直接卡死,尤其在低配设备或 Safari 上掉帧严重。真正可用的方案是用 canvas 逐帧绘制——它不创建 DOM 节点,内存占用低,60fps 稳定。关键不是“怎么动”,而是“怎么控制数量和生命周期”。
实操建议:
立即学习“前端免费学习笔记(深入)”;
x、y、size、speed、opacity 属性ctx.clearRect(0, 0, canvas.width, canvas.height) 清屏,再循环绘制:用 ctx.beginPath() + ctx.arc() 画圆点,或用 ctx.fillText('❄', x, y) 渲染符号(字体加载需兜底)y += speed;超出底部后重置到顶部,x 加随机偏移模拟风向飘动requestAnimationFrame 外层套 setTimeout,会丢帧;直接裸调即可requestAnimationFrame 必须和设备像素比对齐,否则模糊或抖动Canvas 在高清屏上默认按 CSS 像素渲染,雪花边缘发虚、移动时出现“跳帧感”。根本原因是 canvas 的 width/height 属性值没随 window.devicePixelRatio 缩放。
实操建议:
立即学习“前端免费学习笔记(深入)”;
const dpr = window.devicePixelRatio || 1
canvas.width = canvas.offsetWidth * dpr,canvas.height = canvas.offsetHeight * dpr
ctx.scale(dpr, dpr) 让绘图坐标系回归 CSS 像素单位,否则所有 x/y 都要手动乘 dpr
width/height,不能只改 CSS 样式全用 Math.random() 会导致大量雪花挤在中间、边缘稀疏,或忽快忽慢破坏视觉节奏。人眼对“自然下落”的判断其实很敏感。
实操建议:
立即学习“前端免费学习笔记(深入)”;
Math.random() * 2 + 1(1–3px),避免出现 >4px 的突兀大点Math.random() * 0.5 + 0.2(0.2–0.7),太透看不见,太实像雨滴Math.random() * 1.5 + 0.8(0.8–2.3px/frame),再叠加一个缓慢的 sin(x * 0.001) 模拟微风摆动visibilitychange,否则切后台还狂耗电手机切到其他 App 后,requestAnimationFrame 在部分 Android 浏览器里仍持续触发,CPU 温度飙升,用户秒关网页。这不是 bug,是规范允许行为。
实操建议:
立即学习“前端免费学习笔记(深入)”;
document.addEventListener('visibilitychange', () => { if (document.hidden) cancelAnimationFrame(animId); })
setInterval 补帧——时间差会导致雪花位置突变visibilitychange,可加兜底:检测连续 5 秒无用户交互(mousemove/touchstart)后暂停最易被忽略的是 canvas 尺寸重置逻辑——很多人只写一次初始化,窗口缩放后雪花被拉伸变形,还以为是动画代码有问题。实际只要漏了 resize 时的 width/height 重赋值和 ctx.scale 重设,整个效果就废了一半。