应使用 requestAnimationFrame 替代 setTimeout/setInterval 实现动画,因其能精准对齐设备刷新率、自动暂停省电、需递归调用;配合缓动函数、避免布局抖动、统一 transform 操作及妥善清理动画状态,才能保障流畅 60fps。

用 requestAnimationFrame 替代 setTimeout 或 setInterval
浏览器默认以约 60fps 渲染画面,requestAnimationFrame 能精准对齐这一节奏,而 setTimeout 的执行时机不可控,容易跳帧或卡顿。
常见错误是写成:setInterval(() => { update(); render(); }, 16) —— 实际延迟受 JS 主线程阻塞影响,16ms 往往无法保证。
-
requestAnimationFrame自动适配设备刷新率(比如 iPad Pro 的 120Hz) - 页面后台运行时自动暂停,省电且不消耗资源
- 必须在回调中递归调用自身才能持续动画:
function animate() { update(); render(); requestAnimationFrame(animate); }
动画值要用缓动函数(easing),别直接线性插值
人眼对匀速运动敏感,会觉得“机械”;真实物理运动多有加速度变化。直接用 (end - start) * progress 是线性插值,平滑度差。
推荐从简单起步:easeOutQuad(二次缓出)或 easeInOutCubic,比 CSS 的 ease 更可控。
立即学习“Java免费学习笔记(深入)”;
- 示例:位移动画中,用
progress = 1 - Math.pow(1 - t, 3)比t本身更自然 - 避免在每帧重复计算复杂 easing 公式,可预生成查表数组(适合固定时长动画)
- 注意:CSS transition 的
ease是贝塞尔曲线,JS 中等效写法是BezierEasing(0.25, 0.46, 0.45, 0.94),但多数场景用幂函数足够
避免强制同步布局(Layout Thrashing)
在单帧内反复读写 DOM 几何属性(如 offsetTop、getBoundingClientRect()),会触发浏览器反复重排重绘,直接拖垮帧率。
典型错误模式:el.style.left = el.offsetLeft + 1 + 'px'; —— 每次 offsetLeft 都强制同步计算布局。
- 把读取操作集中到帧开头(如
requestAnimationFrame回调最前面) - 把写入操作集中到帧末尾,或统一用
transform(GPU 加速,不触发 layout) - 优先用
transform: translateX()而非left/top,尤其动画元素较多时
动画状态要可中断、可复位,别堆叠定时器
用户快速连续触发动画(比如 hover 多次),若没清理前一个 requestAnimationFrame ID,会导致多个动画逻辑并发执行,数值错乱、CPU 升高。
关键不是“怎么开始”,而是“怎么干净地结束”。
- 每次启动新动画前,先调用
cancelAnimationFrame(this.rafId) - 用布尔标记
isAnimating防止重复启动 - 动画中途跳转目标值时,应基于当前实际位置和时间重新计算起始态,而不是硬设初始值
平滑不是靠堆参数或加帧率,而是让每一帧的计算轻量、读写分离、状态可控。最容易被忽略的是 layout thrashing —— 它不会报错,但会让 60fps 的动画掉到 30fps 以下,且很难定位。











