JS高精度动画 API—requestAnimationFrame(简称 RAF)

124 阅读4分钟

requestAnimationFrame(简称 RAF)是浏览器提供的高精度动画 API,专门用于优化网页动画渲染,相比 setTimeout/setInterval 更流畅、更省性能。其核心思想是让动画与浏览器的刷新频率同步(通常为 60Hz,即每 16.67ms 刷新一次),避免动画卡顿。

一、核心作用与优势

1. 解决 setTimeout/setInterval 的痛点

  • 定时器的 delay 是 “最小延迟”,无法与浏览器刷新同步,可能导致动画跳帧(如 60Hz 下延迟 10ms,会与刷新周期错位)。
  • RAF 由浏览器主动调度,每次刷新屏幕前执行回调,确保动画帧与刷新频率一致(无跳帧)。

2. 关键优势

  • 同步刷新:与浏览器重绘周期同步,动画更流畅(60fps 最优体验)。
  • 自动节流:后台标签页或隐藏元素时,RAF 会暂停执行,节省 CPU/GPU 资源(定时器仍会执行)。
  • 精准计时:回调函数接收一个 “当前时间戳” 参数,可精确计算动画进度(避免定时器累积误差)。

二、基本语法

// 注册动画帧回调,返回一个唯一 ID(用于取消)
const requestId = requestAnimationFrame(callback);

// 取消动画帧(类似 clearTimeout)
cancelAnimationFrame(requestId);
  • callback:每次刷新前执行的函数,接收一个参数 timestamp(DOMHighResTimeStamp 类型,高精度时间戳,单位 ms)。
  • 返回值 requestId:动画帧的唯一标识,用于后续取消。

三、基础示例:简单动画

用 RAF 实现一个 “方块向右移动” 的动画:

<div id="box" style="width: 50px; height: 50px; background: red; position: absolute;"></div>

<script>
const box = document.getElementById('box');
let position = 0; // 方块初始位置

// 动画回调函数
function animate(timestamp) {
  // 更新位置(每次移动 2px)
  position += 2;
  box.style.left = position + 'px';

  // 边界判断:位置 < 500px 时继续执行
  if (position < 500) {
    requestAnimationFrame(animate); // 递归注册下一次动画帧
  }
}

// 启动动画
requestAnimationFrame(animate);
</script>
  • 动画会以 60fps 流畅执行,方块匀速向右移动,直到 left 达到 500px。
  • 若切换到后台标签页,动画会暂停,切回后继续执行(节省资源)。

四、进阶:控制动画速度与进度

RAF 的 timestamp 参数可用于精确控制动画速度(避免受刷新频率影响),例如实现 “2 秒内移动 500px” 的匀速动画:

<div id="box" style="width: 50px; height: 50px; background: blue; position: absolute;"></div>

<script>
const box = document.getElementById('box');
let startTimestamp; // 动画开始时间戳

function animate(timestamp) {
  // 记录动画开始时间(仅第一次执行时)
  if (!startTimestamp) startTimestamp = timestamp;

  // 计算动画已执行时间(ms)
  const elapsed = timestamp - startTimestamp;

  // 动画总时长(2000ms),总距离(500px),计算当前位置
  const duration = 2000;
  const totalDistance = 500;
  const progress = Math.min(elapsed / duration, 1); // 进度(0~1)
  const currentPosition = progress * totalDistance;

  // 更新样式
  box.style.left = currentPosition + 'px';

  // 进度 < 1 时继续执行(未结束)
  if (progress < 1) {
    requestAnimationFrame(animate);
  }
}

// 启动动画
requestAnimationFrame(animate);
</script>
  • 无论浏览器刷新频率是 60Hz 还是 120Hz,动画都会在 2 秒内完成 500px 移动(速度统一)。
  • Math.min(elapsed / duration, 1) 确保进度不超过 1(避免动画超出目标位置)。

五、与 setTimeout/setInterval 的对比

特性requestAnimationFramesetTimeout/setInterval
执行时机浏览器刷新前(同步重绘周期)延迟指定时间后(异步,不同步)
动画流畅度高(60fps 无跳帧)可能卡顿(延迟错位导致跳帧)
后台运行暂停执行(省资源)继续执行(浪费资源)
计时精度提供高精度时间戳(无累积误差)依赖延迟参数(有累积误差)
适用场景网页动画、Canvas/SVG 动画非动画类延迟任务(如倒计时提示)

六、常见应用场景

  1. DOM 动画:元素位移、缩放、透明度变化等(替代 setTimeout 控制样式)。
  2. Canvas/SVG 动画:游戏画面、数据可视化图表(如动态折线图)。
  3. 滚动动画:平滑滚动、滚动触发的动画(比 scrollTo 更流畅)。
  4. 性能优化:批量 DOM 操作(将 DOM 修改放入 RAF 回调,避免强制重绘)。

示例:用 RAF 优化批量 DOM 操作(避免多次重绘):

// 批量修改多个元素样式(放入 RAF 回调,仅触发一次重绘)
function batchUpdate() {
  const elements = document.querySelectorAll('.item');
  elements.forEach((el, index) => {
    el.style.opacity = index / elements.length;
  });
}

// 确保 DOM 修改在同一帧完成,减少重绘次数
requestAnimationFrame(batchUpdate);

七、兼容性与降级方案

  • 支持所有现代浏览器(Chrome 24+、Firefox 23+、Safari 6.1+、Edge 12+)。
  • IE9 及以下不支持,需降级为 setTimeout
// 兼容方案:优先使用 RAF,否则用 setTimeout 模拟(16ms 接近 60Hz)
const requestAnimFrame = window.requestAnimationFrame ||
  function(callback) {
    return setTimeout(callback, 16);
  };

const cancelAnimFrame = window.cancelAnimationFrame ||
  function(requestId) {
    clearTimeout(requestId);
  };

// 使用兼容版 API
requestAnimFrame(animate);

八、总结

requestAnimationFrame 是网页动画的 “最优解”,核心特点:

  • 与浏览器刷新同步,动画流畅(60fps)。
  • 自动节流,节省资源。
  • 提供高精度时间戳,便于控制动画进度。

适用场景:所有需要视觉流畅的动画(DOM 动画、Canvas 游戏、数据可视化等);非动画类延迟任务(如延迟提示)仍可用 setTimeout

实际开发中,建议优先使用 RAF 替代定时器实现动画,提升用户体验和性能。