javascript动画怎样实现平滑效果?【教程】

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

requestAnimationFrame 替代 setTimeoutsetInterval

浏览器默认以约 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 更可控。

  • 示例:位移动画中,用 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 几何属性(如 offsetTopgetBoundingClientRect()),会触发浏览器反复重排重绘,直接拖垮帧率。

典型错误模式: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 以下,且很难定位。