requestAnimationFrame


一、核心概念解析

定义
requestAnimationFrame (rAF) 是浏览器提供的原生 API,用于在下一次重绘前执行动画更新,确保动画与浏览器的绘制周期同步。

设计目标

  • 🎯 解决 setTimeout/setInterval 动画的卡顿、掉帧问题
  • ⚡ 优化性能,减少不必要的渲染
  • 🔋 页面不可见时自动暂停(节省资源)

二、核心特性与优势

特性 说明 优势
帧率同步 自动匹配显示器刷新率(60Hz=16.6ms/帧) 流畅无撕裂
智能休眠 页面隐藏时自动暂停 节省 CPU/GPU 资源
精确时间控制 回调接收高精度时间戳(DOMHighResTimeStamp 精确计算动画进度
批量执行 同一帧中所有 rAF 回调集中处理 避免布局抖动
渲染前执行 在浏览器执行样式计算和布局之后,绘制之前 保证最新状态渲染

三、基础使用模式

let startTime;
let animationId;

function animate(timestamp) {
  // 初始化时间基准
  if (!startTime) startTime = timestamp;

  // 计算动画进度(0-1)
  const progress = Math.min((timestamp - startTime) / 2000, 1);

  // 更新动画状态
  element.style.transform = `translateX(${progress * 500}px)`;

  // 继续动画循环
  if (progress < 1) {
    animationId = requestAnimationFrame(animate);
  }
}

// 启动动画
animationId = requestAnimationFrame(animate);

// 停止动画
function stopAnimation() {
  cancelAnimationFrame(animationId);
}

四、与 setTimeout 的对比

特性 requestAnimationFrame setTimeout
执行时机 下一帧渲染前 延迟时间后(不可靠)
刷新率适配 自动匹配屏幕刷新率 固定时间间隔
隐藏页面处理 自动暂停 持续执行
性能影响 低(浏览器优化) 易导致布局抖动
时间精度 微秒级(performance.now() 毫秒级
函数调用 每帧集中执行所有回调 独立定时器

五、最佳实践与高级技巧

1. 动画生命周期管理

class AnimationController {
  constructor() {
    this.rafId = null;
    this.isRunning = false;
  }

  start(callback) {
    if (this.isRunning) return;

    this.isRunning = true;
    const step = (timestamp) => {
      if (!this.isRunning) return;

      callback(timestamp);
      this.rafId = requestAnimationFrame(step);
    };

    this.rafId = requestAnimationFrame(step);
  }

  stop() {
    this.isRunning = false;
    cancelAnimationFrame(this.rafId);
  }
}

2. FPS 控制(节流渲染)

const targetFPS = 30;
let lastTime = 0;

function throttledUpdate(timestamp) {
  if (timestamp - lastTime > 1000 / targetFPS) {
    // 执行渲染逻辑
    renderScene();
    lastTime = timestamp;
  }
  requestAnimationFrame(throttledUpdate);
}

requestAnimationFrame(throttledUpdate);

3. 滚动性能优化

let ticking = false;

window.addEventListener('scroll', () => {
  if (!ticking) {
    requestAnimationFrame(() => {
      updatePosition(); // DOM操作
      ticking = false;
    });
    ticking = true;
  }
});

六、性能优化场景

1. Canvas 动画优化

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 绘制逻辑
  drawParticles();

  requestAnimationFrame(draw);
}

requestAnimationFrame(draw);

2. 复杂计算分帧执行

const heavyTasks = [...Array(1000)].map((_, i) => () => processItem(i));

function runTasks(timestamp) {
  const start = performance.now();

  while (heavyTasks.length > 0 && performance.now() - start < 8) {
    heavyTasks.shift()();
  }

  if (heavyTasks.length > 0) {
    requestAnimationFrame(runTasks);
  }
}

requestAnimationFrame(runTasks);

七、面试高频问题

1. 为什么 rAF 比 setTimeout 更适合动画?

:rAF 与浏览器渲染周期同步,自动适应刷新率,在页面隐藏时暂停执行,避免不必要的计算和布局抖动,提供更流畅的动画体验。

2. 如何实现动画暂停/继续功能?

let paused = false;
let animationId;

function animate(timestamp) {
  if (paused) return;

  // 动画逻辑
  updateAnimation(timestamp);

  animationId = requestAnimationFrame(animate);
}

function togglePause() {
  paused = !paused;
  if (!paused) {
    animationId = requestAnimationFrame(animate);
  }
}

3. rAF 回调函数参数的作用?

:参数是 DOMHighResTimeStamp 类型的时间戳,表示回调被触发的时间(精确到微秒)。用于:

  • 计算动画的精确时长
  • 实现与时间相关的动画效果
  • 性能监控和帧率计算

4. 如何检测当前帧率?

let lastTime = performance.now();
let frameCount = 0;

function detectFPS(timestamp) {
  frameCount++;

  if (timestamp >= lastTime + 1000) {
    console.log(`FPS: ${frameCount}`);
    frameCount = 0;
    lastTime = timestamp;
  }

  requestAnimationFrame(detectFPS);
}

requestAnimationFrame(detectFPS);

5. rAF 与微任务的执行顺序?

  1. 事件处理
  2. 微任务(Promise、MutationObserver)
  3. requestAnimationFrame
  4. 样式计算/布局/绘制
  5. requestIdleCallback

八、常见错误与陷阱

  1. 递归调用缺失:忘记在回调中重新调用 rAF
  2. 阻塞渲染:单次回调执行超过 16ms
    // 错误:长时间阻塞
    function animate() {
      heavyCalculation(); // 耗时>16ms
      requestAnimationFrame(animate);
    }
    
  3. 布局抖动:在 rAF 中混用读写操作
    // 错误:强制同步布局
    function animate() {
      element.style.width = '100px'; // 写
      const width = element.offsetWidth; // 读(触发重排)·
      requestAnimationFrame(animate);
    }
    
  4. 内存泄漏:未在组件卸载时取消 rAF

    // React 示例
    useEffect(() => {
      let id;
      const animate = () => {
        /* ... */
        id = requestAnimationFrame(animate);
      };
      id = requestAnimationFrame(animate);
    
      return () => cancelAnimationFrame(id);
    }, []);
    

九、面试回答策略

1. 原理优先

"rAF 的核心价值在于它将动画执行与浏览器的渲染管道同步。浏览器每帧执行:

  1. JS 执行 → 2. 样式计算 → 3. 布局 → 4. 绘制 rAF 回调在步骤 1 末尾执行,确保动画更新在渲染前完成"

2. 性能数据支撑

"在我们的电商首页轮播图优化中:

  • 使用 setTimeout:平均 FPS 45,CPU 占用率 38%
  • 改用 rAF 后:FPS 稳定 60,CPU 占用降至 22%"

3. 框架整合展示

// Vue 组合式 API
import { onMounted, onUnmounted } from 'vue';

export function useAnimation(callback) {
  let rafId;

  const start = () => {
    const step = (timestamp) => {
      callback(timestamp);
      rafId = requestAnimationFrame(step);
    };
    rafId = requestAnimationFrame(step);
  };

  onMounted(start);
  onUnmounted(() => cancelAnimationFrame(rafId));

  return { start };
}

4. 高级场景应对

"在 VR 展示项目中,我们处理 120Hz 高刷屏:

  1. 使用 window.matchMedia('(dynamic-range: high)') 检测高刷屏
  2. 动态调整 rAF 中的物理计算步长
  3. 使用 performance.now() 精确控制时间增量"

💡 面试金句
"requestAnimationFrame 是浏览器赋予开发者的动画节拍器——它让我们的代码与显示器的刷新保持完美共振,将生硬的机械运动转化为丝滑的视觉体验。掌握它,就掌握了高性能动画的脉搏。"

Copyright © Jun 2025 all right reserved,powered by Gitbook该文件修订时间: 2025-07-03 17:35:08

results matching ""

    No results matching ""

    results matching ""

      No results matching ""