优雅的 requestAnimationFrame

动画

这篇文章咱们来聊聊动画的实现方法。前端动画总体上分为两类 css 动画和 JS 动画,css 动画就是通过设置 css 属性实现,而 JS 动画就是利用 JavaScript 操控 DOM 的能力实现动画

css动画

这里简单介绍一下 css 动画

transition

transition 过渡动画与类名绑定,不会自动执行只能在满足触发条件是才会执行,并且不能重复执行,除非你再次满足触发条件。并且每次只能设置一个属性的变化。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: aqua;
    }
    #test:hover {
      width: 200px;
      transition: width 1s;
    }
  </style>
</head>
<body>
  <div id="test"></div>
</body>
</html>

animation

animation 动画就很好的解决了上述问题,它能够自动执行、重复执行,也能够同时设置多个属性的变化,并且还可以设置多个中间态

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: aqua;
      animation: animate 1s linear;
    }
    @keyframes animate {
      0% {
        width: 100px;
        height: 100px;
      }
      50% {
        width: 160px;
        height: 160px;
      }
      100% {
        width: 300px;
        height: 300px;
      }
    }
  </style>
</head>
<body>
  <div id="test"></div>
</body>
</html>

但是因为 css 动画只能在现有的属性上让我们设置不同的值,然后执行。这就给 css 动画带来了限制,因为在开发中可能会遇到变化的属性是不固定的情况,此时 css 动画就很乏力了。并且 css 不能定义很复杂的动画。

JS 动画

因为 css 动画由以上诸多限制,所以有的时候我们不得不使用 JS 来控制动画流程。

早期JS动画

早期的 JS 动画是通过 settimeout 和 setInterval 执行的。这里时间间隔设置为 16.6ms 的原因是一般电脑的刷新率为 60 hz,也就是说 1s 屏幕会刷新 60 次,1000ms / 60 近似等于 16.6ms,这样理想情况下的动画最为平滑流畅。

setInterval 动画

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="./test.js"></script>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: aqua;
    }
  </style>
</head>
<body>
  <div id="test"></div>
  <script>
    const oDiv = document.getElementById('test');
    let width = 100;
    const animate = setInterval(() => {
      width++;
      oDiv.style.width = width + 'px';
      if (width >= 200) {
        clearInterval(animate);
      }
    }, 16.6) 
  </script>
</body>
</html>

setTimeout 动画

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="./test.js"></script>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: aqua;
    }
  </style>
</head>
<body>
  <div id="test"></div>
  <script>
    const oDiv = document.getElementById('test');
    let width = 100;
    const animate = () => {
      width++;
      oDiv.style.width = width + 'px';
      if (width <= 200) {
        setTimeout(animate, 16.6);
      }
    }
    setTimeout(animate, 16.6);
  </script>
</body>
</html>

上面说了时间间隔为 16.6ms 的动画最为流畅平滑,但是使用 setInterval 和 setTimeout 并一定能满足要求,我们知道 JS 的事件循环机制(不知道的可以看看这篇博客JS基础——捋一捋 JavaScript 事件循环机制), setInterval 和 setTimeout 是两个宏任务,这样就会每次间隔 16.6ms 就将回调函数推进宏任务队列,等待执行,注意这里并不是间隔 16.6ms 一定会执行回调函数,因此若一轮事件循环中同步任务执行时间过长就会导致动画卡顿。

requestAnimationFrame

因为 setInterval 和 setTimeout 实现动画的缺陷,我们就在思考,因为屏幕刷新率电脑的一个物理特性,并不会随你使用的编程语言变化而变化,那么有没有可能拿到屏幕的刷新时机,在每次刷新的时候执行一次回调函数呢?

基于这个想法,有人提出了 requestAnimationFrame 方法,接收一个函数参数,这个函数参数就是每次屏幕刷新前要执行的回调函数。requestAnimationFrame 方法会通知浏览器 JS 要执行动画了,在屏幕每次刷新前调用函数参数

requestAnimationFrame 方法本质上是浏览器有一个没有长度限制的队列,当调用 requestAnimationFrame 方法就会向队列中推入回调函数,该回调函数会在下一次重绘之前调用。个人理解它和 setInterval 原理上非常类似,只是回调函数的执行时机不同。

requestAnimationFrame 用法

它的用法和 setTimeout 的用法很像。但是它不用传入时间间隔(因为时间间隔是固定的),它的返回值是一个整数,是回调列表中的唯一 ID,作用是用来取消动画的

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="./test.js"></script>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: aqua;
    }
  </style>
</head>
<body>
  <div id="test"></div>
  <script>
    const oDiv = document.getElementById('test');
    let width = 100;
    const animate = () => {
      width++;
      oDiv.style.width = width + 'px';
      if (width <= 200) {
        requestAnimationFrame(animate);
      }
    }
    requestAnimationFrame(animate);
  </script>
</body>
</html>

cancelAnimationFrame

本方法是用来取消动画的。上面的 demo 使用本方法如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="./test.js"></script>
  <style>
    #test {
      width: 100px;
      height: 100px;
      background-color: aqua;
    }
  </style>
</head>
<body>
  <div id="test"></div>
  <script>
    const oDiv = document.getElementById('test');
    let width = 100, end = 0;
    const animate = () => {
      width++;
      oDiv.style.width = width + 'px';
      end = window.requestAnimationFrame(animate);
      if (width >= 200) {
        window.cancelAnimationFrame(end);
      }
    }
    window.requestAnimationFrame(animate);
  </script>
</body>
</html>

requestAnimationFrame 还有两个优势:

  • 当你在执行动画时,但是你并没有浏览动画那个页面,那么该函数就会暂停执行,也就是动画会暂停,当你切回来后会继续执行动画,这将提高电脑的性能,让你的电脑有精力去做别的事。
  • 当在一帧中频繁操作 DOM 后,使用本方法 DOM 将只会被修改一次,最后的值就是所有 DOM 操作最后的结果,相当于一个节流的操作了。

测测电脑刷新率

运行将下面这段代码,你就可以得到你的电脑刷新率哦,可以多运行几次,更准确。

let count = 0, m = null;
function refresh() {
  count++;
  m = window.requestAnimationFrame(refresh);
}

window.requestAnimationFrame(refresh)
function getResult() {
  console.log('你的电脑刷新率为:' + count + 'Hz');
  window.cancelAnimationFrame(m);
}
setTimeout(getResult, 1000)

总结

requestAnimationFrame 方法解决了传统 JS 动画的问题,它的 API 也很简单,同时还有节流和离开暂停动画的效果,实在优雅啊。我是孤城浪人,一名正在前端路上摸爬滚打的菜鸟,欢迎你的关注。

<think>好的,用户现在想了解如何使用requestAnimationFrame进行动画优化或实现平滑过渡效果。我需要结合之前提供的引用资料,整理出一个清晰的回答。首先,得先回忆一下关于requestAnimationFrame的知识点,以及引用中的相关内容。 根据引用[1],requestAnimationFrame适合用于平滑动画,因为它会在每个浏览器刷新帧前更新状态,减少不必要的渲染。引用[4]和[5]提到,相比setTimeout和setInterval,requestAnimationFrame更高效,因为它与浏览器刷新同步,减少重排重绘,同时在不可见元素中暂停,节省资源。 用户可能需要具体的使用步骤和示例代码。所以应该分步骤解释,比如基本用法、优化动画循环、平滑过渡的实现,以及性能优化的方法。引用[3]中的@keyframes可能和CSS动画相关,但用户的问题更偏向于JavaScript的实现,所以重点放在JS部分。 还要注意用户提到的“生成相关问题”,所以最后需要准备几个相关问题,比如与setTimeout的区别、性能对比、兼容性处理等。引用[2]中提到的passive事件和scroll事件优化可能可以作为相关问题,但用户问题主要围绕动画优化,可能需要调整。 需要确保回答结构清晰,分步骤说明,每个步骤有代码示例和解释。同时,引用需要正确标注,比如引用[1]、[5]等提到的优势要自然地插入到回答中,比如在基本用法或优势部分提到。 另外,要注意用户提到的系统级指令,比如行内数学公式用$...$,但这个问题可能不太涉及数学公式,所以可能不需要。但如果有涉及性能分析时的帧率计算,可能需要用,比如60Hz对应16ms,可以写成$16\text{ms}$。 最后,检查LaTeX语法是否正确,中文回答,以及相关问题的生成是否符合要求。确保没有使用Markdown格式,代码块用正确的格式,引用标识放在对应的位置,比如在解释优势时引用[5]。</think>以下是使用`requestAnimationFrame`进行动画优化或实现平滑过渡效果的详细步骤: --- ### 一、基本用法 `requestAnimationFrame`通过递归调用自身实现动画循环,与浏览器刷新频率同步(通常为$60\text{Hz}$,即每$16.67\text{ms}$执行一次)[^5]。 ```javascript function animate() { // 更新动画状态(例如元素位置、透明度等) element.style.left = currentPosition + "px"; // 递归调用以保持动画循环 requestAnimationFrame(animate); } // 启动动画 requestAnimationFrame(animate); ``` --- ### 二、优化动画性能 1. **集中DOM操作** 将所有DOM修改操作集中在一次重绘中执行,避免多次触发布局计算: ```javascript function update() { element1.style.transform = `translateX(${x}px)`; element2.style.opacity = opacity; requestAnimationFrame(update); } ``` 2. **自动暂停不可见动画** 当页面隐藏或最小化时,浏览器会自动暂停`requestAnimationFrame`,减少资源消耗[^5]。 --- ### 三、实现平滑过渡效果 以元素水平移动为例,通过时间戳控制动画进度: ```javascript let startTime; const duration = 2000; // 动画持续2秒 function smoothMove(timestamp) { if (!startTime) startTime = timestamp; const progress = timestamp - startTime; const distance = (progress / duration) * 100; // 移动总距离100px if (progress < duration) { element.style.transform = `translateX(${distance}px)`; requestAnimationFrame(smoothMove); } } requestAnimationFrame(smoothMove); ``` --- ### 四、对比传统方法 | 方法 | 问题 | `requestAnimationFrame`优势 | |-----------------------|----------------------------------------|--------------------------------------| | `setTimeout/setInterval` | 可能因时间不精准导致卡顿或跳帧[^4] | 与刷新率同步,避免帧丢失[^5] | | CSS动画 | 复杂逻辑控制困难 | 可通过编程实现动态调整参数 | --- ### 五、兼容性处理 通过优雅降级支持旧浏览器: ```javascript const requestAnim = window.requestAnimationFrame || window.webkitRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000/60); }; ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值