深入浅出requestAnimationFrame

requestAnimationFrame (通常简写为 rAF) 这个浏览器方法。我会用一个生动的比喻开始,然后逐步深入技术细节。

一、一个生动的比喻:电影放映机

想象一下,你想在浏览器里做一个流畅的动画,就像播放一部电影。

  • 旧方法 (setTimeout / setInterval):

    • 你就像一个新手放映员,手里拿着一堆电影胶片(动画的每一帧)。

    • 你买了一个闹钟 (setTimeout),设定它每 16.7 毫秒响一次(因为 1000ms / 60帧 ≈ 16.7ms)。

    • 闹钟一响,你就赶紧把下一张胶片放到放映机上。

    • 问题来了:

      1. 不精准:你的闹钟可能响得太早或太晚。如果放映机(浏览器)还没准备好放下一帧,你放了也是白放(浪费资源);如果放映机已经准备好了,你的闹钟却晚了,那么那一帧就会被跳过,导致画面卡顿(掉帧)。

      2. 浪费资源:即使观众都走了(你切换到了其他浏览器标签页),你的闹钟还在执着地响,你还在傻傻地换胶片,白白浪费电力(CPU 和电池)。

  • 新方法 (requestAnimationFrame):

    • 你现在是一个聪明的放映员。

    • 你不再自己设闹钟,而是直接告诉放映机(浏览器):"嘿,你下次准备好放映新胶片之前,叫我一下,我把新胶片给你。"

    • 于是,浏览器在每一次要"绘制"新画面之前,就会主动通知你。你就在那个时机把计算好的新画面内容(比如更新元素的位置)交给它。

    • 好处显而易见:

      1. 时机完美:你的所有工作都正好赶在浏览器需要的时候完成,不多不少,动画如丝般顺滑。

      2. 智能节能:如果观众走了(标签页被隐藏或最小化),放映机就暂停放映了,它自然也不会再叫你,你的工作也就自动暂停了,节省了宝贵的系统资源。


二、requestAnimationFrame 是什么?

requestAnimationFrame 是一个浏览器提供的 Web API,它的作用是请求浏览器在下一次重绘(repaint)之前,执行一个你指定的回调函数

简单来说:你不是命令浏览器"立即"或"在XX毫秒后"更新动画,而是请求浏览器在它自己认为最合适的时机(也就是下一次渲染新画面前)来执行你的更新逻辑。


三、为什么它比 setTimeout / setInterval 更好?

特性 setTimeout / setInterval requestAnimationFrame (rAF)
同步机制 与浏览器渲染异步。它有自己的内部时钟,不关心浏览器是否准备好渲染。 与浏览器渲染同步。它的执行时机由浏览器显示器的刷新率决定。
性能/流畅度 容易导致掉帧无效渲染,因为执行时机不精确,可能在一帧内执行多次,或错过某一帧。 最大程度地保证流畅。浏览器会确保你的回调在每一帧的绘制周期内只执行一次,避免了卡顿。
资源消耗 即使页面在后台或不可见,定时器依然会持续执行,消耗 CPU 和电池。 当页面在后台或不可见时,浏览器会自动暂停 执行,非常节能
浏览器优化 浏览器无法优化。 浏览器可以将多个 rAF 回调(来自不同组件或库)合并到一次重绘中,提高渲染效率,避免"布局抖动"(Layout Thrashing)。

四、如何使用 requestAnimationFrame

它的用法非常简单,通常用于创建一个动画循环。

基础示例:让一个方块向右移动

html 复制代码
<div id="box" style="position: absolute; left: 0; width: 50px; height: 50px; background: skyblue;"></div>

<script>
  const box = document.getElementById('box');
  let position = 0;
  let animationFrameId;

  // 动画循环函数
  function animate() {
    // 1. 更新状态(例如,位置)
    position += 2;
    box.style.left = position + 'px';

    // 2. 如果动画没有结束,请求下一帧
    if (position < 300) {
      // 这是关键!请求浏览器在下一次重绘前再次调用 animate 函数
      animationFrameId = requestAnimationFrame(animate);
    }
  }

  // 启动动画
  // 只需要调用一次,就会开启一个由浏览器驱动的循环
  animationFrameId = requestAnimationFrame(animate);

  // 如果需要,可以随时停止动画
  // setTimeout(() => {
  //   cancelAnimationFrame(animationFrameId);
  //   console.log('动画已停止');
  // }, 2000);
</script>

代码解析:

  1. 我们定义了一个 animate 函数,它负责单帧的动画逻辑:更新位置,并应用到 DOM 元素上。

  2. animate 函数的末尾,我们再次调用 requestAnimationFrame(animate)。这就像接力赛,这一帧的选手跑完后,把接力棒交给了下一帧的自己,从而形成一个持续的循环。

  3. 这个循环不是死循环,它的执行频率由浏览器控制,通常是每秒 60 次(对于 60Hz 的屏幕)。

  4. requestAnimationFrame 会返回一个 ID,你可以使用 cancelAnimationFrame(id) 来随时终止这个动画循环。

进阶示例:使用时间戳实现匀速动画

requestAnimationFrame 的回调函数会接收一个高精度的时间戳参数(DOMHighResTimeStamp),表示自页面加载以来经过的毫秒数。使用这个时间戳可以创建与帧率无关的、更精确的动画。

javascript 复制代码
const box = document.getElementById('box');

let startTime = null;

const duration = 2000; // 动画持续时间 2000ms

const distance = 300; // 总移动距离 300px

function animate(currentTime) {

  // 记录第一帧的时间

  if (startTime === null) {

    startTime = currentTime;

  }

  // 计算已经过的时间

  const elapsedTime = currentTime - startTime;

  // 计算当前应该在的位置(基于时间的百分比)

  // Math.min 确保动画不会超出

  const progress = Math.min(elapsedTime / duration, 1);

  const newPosition = progress * distance;

  

  box.style.transform = `translateX(${newPosition}px)`;

  // 如果动画还没结束,继续请求下一帧

  if (progress < 1) {

    requestAnimationFrame(animate);

  }

}

// 启动动画

requestAnimationFrame(animate);

这个进阶版本的好处是,无论用户的电脑帧率是 30fps 还是 144fps,这个动画都会在 2 秒内完成,只是高帧率的设备上动画过程会显得更平滑。


五、总结

  1. 是什么? requestAnimationFrame 是一个请求浏览器在下次重绘前执行特定函数的 API。

  2. 为什么用? 为了创建高性能、流畅且节能的 Web 动画。

  3. 核心优势:

* 与刷新率同步:避免掉帧,动画更流畅。

* 智能暂停:页面不可见时自动暂停,节省 CPU/电池。

* 浏览器优化:能将多个动画更新合并为一次重绘。

  1. 怎么用? 创建一个递归调用的动画函数,在函数内部更新画面状态,并调用 requestAnimationFrame 请求下一帧。

所以,下次当你需要用 JavaScript 做任何涉及连续视觉变化(如动画、游戏循环、数据可视化更新)的需求时,requestAnimationFrame 应该是你的首选。

相关推荐
歪歪10018 小时前
在哪些场景下适合使用 v-model 机制?
服务器·前端·javascript·servlet·前端框架·js
亲爱的马哥18 小时前
再见,TDuckX3.0 结束了
前端·后端·github
KrystalMeiling19 小时前
讲解react的useEffect用法,被监听字段的注意事项
前端
渣哥19 小时前
Spring 创建 Bean 的多种方式对比与最佳实践
前端·javascript·面试
SanOrintea19 小时前
electron中进程线程之间通信方式
服务器·javascript·electron
Copper peas19 小时前
axios使用过程
前端·javascript·vue.js
云鹤_19 小时前
【Amis源码阅读】如何将json配置渲染成页面?
前端·低代码
逛逛GitHub19 小时前
飞书多维表格 + 即梦 4.0,打造你的 AI 生图游乐场。
前端·github
行走在顶尖19 小时前
Vue3 基础笔记
前端