粒子系统的那些坑
粒子系统的那些坑
首页那个 WEN 粒子效果,看起来简单,实际上折腾了两三天才调到满意的状态。
初期方案评估
最开始想直接上 Three.js 的 Points,配合 BufferGeometry 来管粒子。但试了一下发现,对于首页这种只需要几千个粒子、不需要 3D 旋转的场景,Three.js 的 overhead 有点大——一个 Points 对象挂上去就得跑一遍完整的渲染管线,shader compilation 还要等。
后来改成纯 Canvas 2D。核心逻辑其实就两步:
1. 用 getImageData 扫描画上去的文字,提取所有不透明像素的坐标
2. 在那些坐标上画圆点
性能瓶颈
扫描本身很快,但动画循环里的性能问题比较隐蔽。第一个版本直接用 ctx.arc 逐个画粒子,在 1080p 下大概 7000 个点,帧率掉到 45fps 左右。排查了一下发现瓶颈不在 draw call 数量,而在 canvas 的像素填充率——每次 clearRect 清空画布再重绘,对 GPU 来说是个不小的开销。
优化方向:
- 降低 clearRect 频率:只在粒子位置变化超过阈值时才重绘,但试下来效果不好,静止状态下粒子也会有 flicker
- 用离屏 Canvas 缓存文字模板:把文字的静态像素位置缓存下来,不用每次重新扫描
- 减少粒子数:把 spacing 从 2 调到 3,粒子数从 7000 降到 3000 左右,肉眼几乎看不出差异
鼠标交互的取舍
粒子跟随鼠标排斥的效果实现起来不复杂——检测鼠标位置,计算距离,超过阈值就施加一个向量偏移。问题出在交互手感上。
一开始用的线性插值:
particle.x += (targetX - particle.x) * 0.1
但 0.1 这个系数不好调:调大了粒子跟着鼠标跑太快像磁铁,调小了又拖尾太严重。后面改成先累加速度再衰减:
vx += (targetX - x) * 0.08
vx *= 0.8
x += vx
这样粒子的运动有惯性感,手感柔很多。
还没解决的问题
手机端把 canvas 直接 display none 了,因为触摸事件模拟鼠标位置的效果体验很差。也许后面会加个 fallback——检测到触摸设备就直接渲染静态粒子不交互相应。