requestIdleCallback 与 requestAnimationFrame

两种浏览器调度 API 的区别与使用场景

问题

requestIdleCallbackrequestAnimationFrame 有什么区别?分别在什么场景下使用?

解答

requestAnimationFrame

在浏览器下一次重绘之前执行回调,通常每秒 60 次(约 16.67ms 一次)。

// 用于流畅动画
function animate() {
  // 更新动画状态
  element.style.transform = `translateX(${position}px)`;
  position += 2;

  if (position < 300) {
    // 继续下一帧
    requestAnimationFrame(animate);
  }
}

requestAnimationFrame(animate);

requestIdleCallback

在浏览器空闲时执行回调,适合低优先级任务。

// 用于非紧急任务
requestIdleCallback((deadline) => {
  // deadline.timeRemaining() 返回当前帧剩余时间(ms)
  while (deadline.timeRemaining() > 0 && tasks.length > 0) {
    // 执行任务
    const task = tasks.shift();
    task();
  }

  // 还有任务,继续排队
  if (tasks.length > 0) {
    requestIdleCallback(processTask);
  }
}, { timeout: 2000 }); // 最多等待 2 秒

执行时机对比

一帧的生命周期(约 16.67ms):

输入事件 → JS 执行 → rAF 回调 → 样式计算 → 布局 → 绘制 → [空闲时间] → rIC 回调
                ↑                                              ↑
        requestAnimationFrame                          requestIdleCallback

完整示例

// 动画任务 - 使用 rAF
function smoothScroll(targetY) {
  const startY = window.scrollY;
  const distance = targetY - startY;
  let startTime = null;

  function step(timestamp) {
    if (!startTime) startTime = timestamp;
    const progress = Math.min((timestamp - startTime) / 500, 1);
    
    window.scrollTo(0, startY + distance * easeOutCubic(progress));
    
    if (progress < 1) {
      requestAnimationFrame(step);
    }
  }

  requestAnimationFrame(step);
}

// 非紧急任务 - 使用 rIC
function sendAnalytics(data) {
  requestIdleCallback(() => {
    // 上报埋点数据,不影响用户交互
    fetch('/analytics', {
      method: 'POST',
      body: JSON.stringify(data)
    });
  }, { timeout: 5000 });
}

// 分片处理大量数据 - 使用 rIC
function processLargeArray(items, callback) {
  const queue = [...items];

  function process(deadline) {
    while (deadline.timeRemaining() > 0 && queue.length > 0) {
      callback(queue.shift());
    }

    if (queue.length > 0) {
      requestIdleCallback(process);
    }
  }

  requestIdleCallback(process);
}

兼容性处理

// requestIdleCallback polyfill
window.requestIdleCallback = window.requestIdleCallback || function(cb) {
  const start = Date.now();
  return setTimeout(() => {
    cb({
      didTimeout: false,
      timeRemaining: () => Math.max(0, 50 - (Date.now() - start))
    });
  }, 1);
};

window.cancelIdleCallback = window.cancelIdleCallback || function(id) {
  clearTimeout(id);
};

关键点

  • 执行时机:rAF 在重绘前执行,rIC 在空闲时执行
  • 调用频率:rAF 每帧必调用(约 60fps),rIC 可能被延迟或跳过
  • 使用场景:rAF 用于动画和 DOM 操作,rIC 用于埋点、预加载等低优先级任务
  • API 差异:rIC 回调接收 deadline 对象,可查询剩余时间;支持 timeout 选项强制执行
  • 兼容性:rAF 支持良好,rIC 在 Safari 中不支持,需要 polyfill