JS `requestAnimationFrame` 实现平滑滚动与动画

各位靓仔靓女们,早上好/下午好/晚上好!我是你们的滚动大师,今天咱们来聊聊如何用 requestAnimationFrame 把网页滚动玩出花,做出丝滑般顺畅的动画效果。准备好了吗?Let’s roll!

第一节:requestAnimationFrame 是个啥玩意儿?

首先,requestAnimationFrame 这玩意儿可不是让你去申请动画基金的(虽然我也想),它是一个浏览器提供的 API,专门用来做动画。

想象一下,你正在看一部电影,电影是由一帧一帧的画面快速播放形成的。我们的网页动画也是一样的道理,我们需要不停地更新画面,才能让它动起来。

requestAnimationFrame 的作用就是告诉浏览器:“嘿,老兄,我这里有个动画要搞,你能不能在下一次屏幕刷新之前,帮我执行一下这个函数?”

浏览器会根据你的显示器的刷新率(比如 60Hz,也就是每秒刷新 60 次)来安排这个函数的执行时间。这样一来,你的动画就能跟上浏览器的节奏,避免卡顿和掉帧。

简单来说,requestAnimationFrame 就像一个闹钟,到点就叫你起来更新动画,而且这个闹钟是浏览器亲自设定的,保证准时准点。

第二节:为什么要用 requestAnimationFrame

你可能会问:“我用 setInterval 或者 setTimeout 不也能做动画吗?干嘛非要用 requestAnimationFrame 这么个拗口的东西?”

问得好!setIntervalsetTimeout 的确可以用来做动画,但它们有几个缺点:

  • 时间不精确: setIntervalsetTimeout 的执行时间并不保证精确,可能会受到其他代码的影响,导致动画卡顿。
  • 浪费资源: 就算页面不可见(比如切换到其他标签页),setIntervalsetTimeout 仍然会继续执行,浪费 CPU 资源。
  • 不同步: 它们无法与浏览器的刷新率同步,可能会出现掉帧或者撕裂现象。

requestAnimationFrame 则完美地解决了这些问题:

  • 同步刷新: 它会与浏览器的刷新率同步,保证动画流畅。
  • 节省资源: 当页面不可见时,requestAnimationFrame 会自动停止执行,节省 CPU 资源。
  • 高性能: 浏览器会对 requestAnimationFrame 进行优化,使其执行效率更高。

总之,requestAnimationFrame 是做网页动画的不二之选。它就像一个专业的舞蹈演员,能完美地配合浏览器的节奏,跳出优雅的动画。

第三节:requestAnimationFrame 的基本用法

requestAnimationFrame 的用法非常简单:

function animate() {
  // 在这里更新动画
  console.log("动画正在运行...");

  // 再次调用 requestAnimationFrame,形成循环
  requestAnimationFrame(animate);
}

// 启动动画
requestAnimationFrame(animate);

这段代码会不停地输出 "动画正在运行…",直到你手动停止它。

让我们来分解一下这段代码:

  1. animate 函数: 这个函数是动画的核心,你需要在里面更新动画的属性。
  2. requestAnimationFrame(animate) 这行代码会告诉浏览器,在下一次屏幕刷新之前,执行 animate 函数。
  3. 循环调用:animate 函数的末尾,我们再次调用 requestAnimationFrame(animate),形成一个循环,让动画持续运行。

就像贪吃蛇游戏一样,蛇头每移动一次,就要再次发出移动指令,才能让它继续前进。

第四节:用 requestAnimationFrame 实现平滑滚动

好了,现在我们来用 requestAnimationFrame 实现一个平滑滚动的效果。

假设我们有一个页面,想要点击一个按钮,让页面平滑地滚动到某个位置。

首先,我们需要获取按钮和目标位置的元素:

<button id="scrollToTopBtn">滚动到顶部</button>
<div style="height: 2000px;"></div> <!-- 模拟页面内容 -->
<div id="targetElement" style="position: absolute; top: 0; left: 0;">顶部</div>
const scrollToTopBtn = document.getElementById("scrollToTopBtn");
const targetElement = document.getElementById("targetElement");

接下来,我们需要一个函数来执行滚动动画:

function scrollToTop(duration) {
  const start = window.pageYOffset; // 滚动开始时的位置
  const target = targetElement.offsetTop; // 目标位置
  const change = target - start; // 总的滚动距离
  let startTime = null;

  function animateScroll(currentTime) {
    if (startTime === null) {
      startTime = currentTime;
    }

    const timeElapsed = currentTime - startTime;
    const run = ease(timeElapsed, start, change, duration); // 计算当前滚动位置
    window.scrollTo(0, run);

    if (timeElapsed < duration) {
      requestAnimationFrame(animateScroll); // 继续滚动
    } else {
      // 滚动结束
      window.scrollTo(0, target); // 确保滚动到精确位置
    }
  }

  requestAnimationFrame(animateScroll);
}

让我们来分析一下这个函数:

  1. scrollToTop(duration) 这个函数接受一个参数 duration,表示滚动动画的持续时间(毫秒)。
  2. start 滚动开始时的位置,也就是当前页面的滚动距离。
  3. target 目标位置,也就是 targetElement 的顶部距离页面顶部的距离。
  4. change 总的滚动距离,也就是 target - start
  5. startTime 动画开始的时间,用于计算已经过去的时间。
  6. animateScroll(currentTime) 这个函数是动画的核心,它会根据当前时间计算出滚动位置,并更新页面的滚动距离。
  7. ease(timeElapsed, start, change, duration) 这是一个缓动函数,用于控制动画的速度。我们稍后会详细讲解。
  8. window.scrollTo(0, run) 这行代码会更新页面的滚动距离,让页面滚动到指定的位置。
  9. requestAnimationFrame(animateScroll) 这行代码会继续执行滚动动画,直到滚动到目标位置。
  10. 滚动结束时window.scrollTo(0, target)这行代码为了保证精确的到达目标位置,因为easing 函数计算出来的值,可能存在一定的误差。
  11. currentTime: requestAnimationFrame 会自动传入一个参数,表示当前时间。

最后,我们需要给按钮添加点击事件监听器:

scrollToTopBtn.addEventListener("click", function() {
  scrollToTop(1000); // 滚动时间为 1 秒
});

现在,当你点击按钮时,页面就会平滑地滚动到顶部。

第五节:缓动函数(Easing Functions)

你可能已经注意到了,在 scrollToTop 函数中,我们使用了一个 ease 函数来控制动画的速度。这个函数叫做缓动函数,它可以让动画看起来更加自然和流畅。

如果没有缓动函数,动画的速度会是线性的,也就是匀速运动。这样的动画看起来会比较生硬。

缓动函数可以改变动画的速度,让它在开始时慢,中间快,结束时慢,或者其他各种各样的效果。

这里我们提供一个常用的缓动函数:

function ease(t, b, c, d) {
  t /= d / 2;
  if (t < 1) return c / 2 * t * t + b;
  t--;
  return -c / 2 * (t * (t - 2) - 1) + b;
}

这个函数实现的是一个 "easeInOutQuad" 缓动效果,也就是在开始和结束时速度较慢,中间速度较快。

让我们来解释一下这个函数的参数:

  • t 当前时间(已经过去的时间)。
  • b 初始值(滚动开始时的位置)。
  • c 总的改变值(总的滚动距离)。
  • d 持续时间(滚动动画的持续时间)。

这个函数会根据这些参数计算出当前应该滚动到的位置。

当然,你也可以使用其他的缓动函数,比如 "easeInQuad"、"easeOutQuad"、"easeInOutCubic" 等等。网上有很多缓动函数的代码可以找到。

第六节:更复杂的动画效果

除了平滑滚动,requestAnimationFrame 还可以用来实现各种各样的动画效果,比如:

  • 元素淡入淡出: 改变元素的透明度,让它逐渐显示或隐藏。
  • 元素移动: 改变元素的位置,让它从一个地方移动到另一个地方。
  • 元素缩放: 改变元素的大小,让它逐渐放大或缩小。
  • 元素旋转: 改变元素的角度,让它旋转。

这些动画效果的原理都是一样的:

  1. 定义动画的属性: 比如透明度、位置、大小、角度等等。
  2. 定义动画的起始值和结束值: 比如透明度从 0 到 1,位置从 (0, 0) 到 (100, 100) 等等。
  3. 使用 requestAnimationFrame 定期更新动画的属性: 根据时间计算出当前应该更新到的值,并应用到元素上。
  4. 使用缓动函数控制动画的速度: 让动画看起来更加自然和流畅。

第七节:使用第三方库简化动画开发

如果你觉得手动编写动画代码太麻烦,可以使用一些第三方库来简化动画开发。

比较流行的动画库有:

  • GreenSock Animation Platform (GSAP): 一个功能强大的动画库,可以实现各种复杂的动画效果。
  • Anime.js: 一个轻量级的动画库,易于使用,适合简单的动画效果。
  • Velocity.js: 一个高性能的动画库,可以实现流畅的动画效果。

这些库都提供了丰富的 API 和预设的动画效果,可以让你快速地创建出漂亮的动画。

第八节:性能优化

虽然 requestAnimationFrame 已经做了很多优化,但我们仍然需要注意一些性能问题,以保证动画的流畅性:

  • 避免在动画中进行大量的计算: 尽量把计算放在动画之外,或者使用 Web Workers 将计算放到后台线程中。
  • 减少 DOM 操作: 频繁的 DOM 操作会导致页面重绘和回流,影响动画的性能。尽量使用 CSS transform 来改变元素的位置和大小。
  • 使用硬件加速: 某些 CSS 属性(比如 transformopacity)可以触发硬件加速,提高动画的性能。
  • 避免内存泄漏: 在动画结束时,记得清理掉不再需要的资源,比如事件监听器和定时器。

第九节:总结

今天我们学习了如何使用 requestAnimationFrame 实现平滑滚动和动画效果。

requestAnimationFrame 是一个强大的 API,可以让你创建出流畅、高性能的网页动画。

希望大家能够掌握 requestAnimationFrame 的用法,并在实际项目中应用它,让你的网页更加生动有趣!

最后,给大家留个小作业:

尝试使用 requestAnimationFrame 实现一个元素淡入淡出的动画效果。

祝大家学习愉快!下次再见!

表格:requestAnimationFrame vs setInterval / setTimeout

特性 requestAnimationFrame setInterval / setTimeout
执行时机 与浏览器刷新同步 不精确
资源消耗 页面不可见时暂停执行 持续执行
性能 浏览器优化 性能较差
动画流畅性 流畅 可能卡顿
用途 动画 定时任务

代码示例:淡入淡出动画

<div id="fadeElement" style="opacity: 0; width: 100px; height: 100px; background-color: red;"></div>
<button id="fadeBtn">淡入</button>

<script>
  const fadeElement = document.getElementById("fadeElement");
  const fadeBtn = document.getElementById("fadeBtn");

  let opacity = 0;
  let fadingIn = true;

  function fadeInOut() {
    if (fadingIn) {
      opacity += 0.01;
      if (opacity >= 1) {
        opacity = 1;
        fadingIn = false;
      }
    } else {
      opacity -= 0.01;
      if (opacity <= 0) {
        opacity = 0;
        fadingIn = true;
      }
    }

    fadeElement.style.opacity = opacity;

    requestAnimationFrame(fadeInOut);
  }

  fadeBtn.addEventListener("click", function() {
    requestAnimationFrame(fadeInOut);
  });
</script>

这个例子展示了一个简单的淡入淡出效果。点击按钮后,红色方块会逐渐显示,然后逐渐消失,循环往复。代码也比较容易理解,大家可以自己尝试修改参数,体验不同的动画效果。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注