应使用 requestAnimationFrame 替代 setTimeout/setInterval 实现动画,因其能精准对齐设备刷新率、自动暂停省电、需递归调用;配合缓动函数、避免布局抖动、统一 transform 操作及妥善清理动画状态,才能保障流畅 60fps。
requestAnimationFrame 替代 setTimeout 或 setInterval
浏览器默认以约 60fps 渲染画面,requestAnimationFrame 能精准对齐这一节奏,而 setTimeout 的执行时机不可控,容易跳帧或卡顿。
常见错误是写成:setInterval(() => { update(); render(); }, 16) —— 实际延迟受 JS 主线程阻塞影响,16ms 往往无法保证。
re
questAnimationFrame 自动适配设备刷新率(比如 iPad Pro 的 120Hz)function animate() { update(); render(); requestAnimationFrame(animate); }
人眼对匀速运动敏感,会觉得“机械”;真实物理运动多有加速度变化。直接用 (end - start) * progress 是线性插值,平滑度差。
推荐从简单起步:easeOutQuad(二次缓出)或 easeInOutCubic,比 CSS 的 ease 更可控。
progress = 1 - Math.pow(1 - t, 3) 比 t 本身更自然ease 是贝塞尔曲线,JS 中等效写法是 BezierEasing(0.25, 0.46, 0.45, 0.94),但多数场景用幂函数足够在单帧内反复读写 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 以下,且很难定位。