核心问题是“怎么管”和“怎么画”,需从渲染链路和资源交付双端优化:用 requestAnimationFrame 替代 setInterval/timeout,抽离 tick 逻辑并时间戳节流,复用对象、用 TypedArray 和整数颜色编码,加距离平方阈值优化连线。

移动端全屏 Canvas 粒子动画卡顿、包体臃肿,核心问题往往不在“粒子多”,而在“怎么管”和“怎么画”。优化要从渲染链路(requestAnimationFrame → 更新 → 绘制)和资源交付(JS 体积、初始化开销)两头抓,而不是堆参数或删粒子。
用对驱动方式,避免定时器硬同步
别用 setInterval 或 setTimeout 控制动画循环。它们与屏幕刷新率无关,容易在低端安卓机上堆积任务、跳帧甚至白屏。必须用 requestAnimationFrame,它由浏览器调度,自动适配设备实际刷新率(如 60fps 或 90fps),且在页面不可见时暂停执行,省电又稳帧。
- 只写一个主动画函数(比如
animate()),末尾递归调用requestAnimationFrame(animate) - 不要在回调里做耗时计算:粒子物理更新、碰撞检测、颜色插值等逻辑,应抽离到独立的
tick()函数中,并用时间戳节流(例如每 16ms 执行一次),而非靠帧数硬控 - 鼠标/触摸响应建议用
throttle(如 60ms 间隔)捕获位置,避免高频触发重计算
精简粒子逻辑,拒绝运行时对象爆炸
移动端内存和 GC 压力敏感。每帧 new 一堆粒子对象、临时数组或颜色对象,几秒后就触发频繁垃圾回收,直接拖垮帧率。
- 粒子属性尽量用字面量对象复用,避免构造函数实例化;生命周期管理用数组下标标记“死亡”,再用
splice或双指针清理,比 filter 更轻 - 移除所有
new Color()、{x, y, vx, vy}这类运行时新建对象操作;颜色可用整数编码(如0xFF33AA),坐标用 TypedArray(Float32Array)存,提升缓存局部性 - 连线逻辑加距离平方阈值(如
dx*dx + dy*dy ),跳过 Math.sqrt,iOS Safari 尤其吃这一口
控制绘制粒度,清空与复用有讲究
全屏 Canvas 在高 DPR(如 iPhone 15 Pro 的 3x)下实际渲染像素达数百万,clearRect 和 fillArc 每帧重复调用,极易成为瓶颈。
-
ctx.clearRect(0, 0, canvas.width, canvas.height)必须带缩放后的宽高,否则留残影;可提前缓存canvas.width和canvas.height,避免每次读取 DOM - 粒子绘制优先用
ctx.fillRect(x, y, size, size)(方形)代替arc + fill(圆形),快 2–3 倍;若必须圆点,用fillRect配合globalAlpha和shadowBlur模拟柔边,更省 - 禁用抗锯齿:
ctx.imageSmoothingEnabled = false,移动端 GPU 对 smooth 处理低效
压缩交付体积,启动即跑不等资源
所谓“2KB 彩点背景能跑起来”,关键不是代码少,而是没依赖、无异步、零配置。
- 把粒子生成、初始位置分布、颜色映射全部写死在 JS 内联脚本里,去掉 JSON 配置解析、fetch 加载、Promise 等 runtime 开销
- 删除所有 console、注释、未使用函数;用 Terser 最小化,目标控制在 3KB 内(含 canvas 初始化和 resize 监听)
- resize 逻辑只重设 canvas.width/height,**必须同步重置粒子坐标范围**(如
particle.x = Math.random() * canvas.width),否则新尺寸下粒子全挤左上角,视觉崩溃


















