前端面试-requestAnimationFrame需要了解的一些事

一、为什么你的动画总是卡顿?

小明最近在开发一个电商网站的轮播图,使用setTimeout实现的动画总是出现轻微卡顿。当他改用requestAnimationFrame后,动画立即变得丝滑流畅。这背后究竟隐藏着怎样的浏览器运行机制?

二、requestAnimationFrame核心原理

1. 与浏览器渲染周期同步

浏览器以16.6ms(60Hz屏幕)为周期进行渲染:

// 传统做法(可能掉帧)
setInterval(() => {
  更新动画
}, 16.6);

// RAF做法(自动同步)
function animate() {
  更新动画
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

2. 智能节流机制

当页面处于后台或隐藏时,RAF会自动暂停执行,节省CPU资源。而setTimeout会持续运行,导致无谓的资源消耗。

三、六大实战应用场景

场景1:丝滑动画实现

function moveElement(element, distance) {
  let start = null;
  
  function step(timestamp) {
    if (!start) start = timestamp;
    const progress = timestamp - start;
    
    // 计算移动距离(缓动函数)
    const move = easeInOutQuad(progress, 0, distance, 1000);
    element.style.transform = `translateX(${move}px)`;
    
    if (progress < 1000) {
      requestAnimationFrame(step);
    }
  }
  
  requestAnimationFrame(step);
}

场景2:大数据量分批渲染

function renderLargeList(items) {
  let index = 0;
  
  function chunkRender() {
    const start = performance.now();
    
    // 每帧最多处理50ms的任务
    while (index < items.length && 
           performance.now() - start < 50) {
      renderItem(items[index++]);
    }
    
    if (index < items.length) {
      requestAnimationFrame(chunkRender);
    }
  }
  
  chunkRender();
}

场景3:精准获取元素布局

function getStableLayout(element) {
  return new Promise(resolve => {
    requestAnimationFrame(() => {
      const rect1 = element.getBoundingClientRect();
      requestAnimationFrame(() => {
        const rect2 = element.getBoundingClientRect();
        rect1.top === rect2.top ? resolve(rect1) : getStableLayout(element);
      });
    });
  });
}

四、性能优化三大法则

  1. 单帧时长控制:保持每帧任务在16ms内完成
  2. 避免强制同步布局:先读后写,批量操作
  3. 使用Web Worker分流计算
// 主线程
const worker = new Worker('calc.js');

function animate() {
  worker.postMessage(data);
  requestAnimationFrame(animate);
}

// Worker线程
self.onmessage = ({data}) => {
  const result = heavyCalculate(data);
  self.postMessage(result);
}

五、高频面试题解析

Q1:requestAnimationFrame的执行时机是什么?
A:在浏览器重绘之前执行,与屏幕刷新率同步(通常16.6ms)

Q2:如何实现动画暂停/恢复?

let animationId = null;
let paused = false;

function toggleAnimation() {
  if (paused) {
    animationId = requestAnimationFrame(animate);
  } else {
    cancelAnimationFrame(animationId);
  }
  paused = !paused;
}

Q3:如何实现60FPS稳定动画?
A:计算时间差动态调整位移量:

let lastTime = 0;

function animate(timestamp) {
  const delta = timestamp - lastTime;
  const step = (delta / 16.6) * baseSpeed;
  updatePosition(step);
  lastTime = timestamp;
  requestAnimationFrame(animate);
}

Q4:为什么RAF比setTimeout更适合做动画?
A:1自动匹配刷新率 2后台标签页暂停 3批量样式更新 4更高优先级

结合上面文章讲的事件循环机制负责处理 JavaScript 中的异步任务。它包含任务队列和调用栈,任务队列又分为宏任务队列和微任务队列。requestAnimationFrame 的回调函数会被插入到一个特殊的队列中,该队列会在每次重绘之前执行。这意味着,requestAnimationFrame 的回调函数会在合适的时机执行,避免不必要的重绘和回流,从而提升性能。

原文链接:,转发请注明来源!