[Js进阶]requestAnimationFrame应用

requestAnimationFrame是一个用于动画的API,它在浏览器刷新率下执行回调,常用于流畅的动画效果,如平滑滚动、倒计时等。作为宏任务,它的执行优先于微任务。文章提供了多个示例,展示了如何利用requestAnimationFrame实现平滑滚动、倒计时、拖拽和渐变效果,并与setTimeout和setInterval进行了比较。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[Js进阶]requestAnimationFrame应用

简介

requestAnimationFrame 是一个用于动画效果的 API,它使用浏览器的刷新率来执行回调函数,通常每秒钟执行 60 次。与 setTimeoutsetInterval 不同的是,requestAnimationFrame 被视为一个特殊的“宏任务”。

宏任务是一个在事件循环队列中排队等待执行的任务。与宏任务不同的是,“微任务”是一个在当前任务完成后立即执行的任务,它通常用于处理异步操作的结果。

在 JavaScript 中,异步操作通常是通过回调函数或 Promise 处理的。当异步操作完成后,回调函数将被添加到微任务队列中,以便在当前任务完成后立即执行。这意味着,微任务的执行顺序优先于下一个宏任务。

对于 requestAnimationFrame,由于它被视为一个宏任务,因此它的执行顺序优先于微任务。这意味着,如果在 requestAnimationFrame 回调函数中添加了微任务,那么这些微任务将在下一个宏任务(即下一个 requestAnimationFrame 回调函数)执行前被执行。

应用场景

requestAnimationFrame主要用于实现流畅的动画效果,它可以在浏览器的重绘周期内执行指定的函数,从而避免由于频繁的重绘导致的性能问题。除了动画之外,requestAnimationFrame还可以应用于以下场景:

  1. 实现平滑滚动效果。使用requestAnimationFrame可以在滚动过程中不断更新滚动位置,并且可以控制滚动的速度和加速度,从而实现更加自然和流畅的滚动效果。
  2. 实现倒计时效果。使用requestAnimationFrame可以控制倒计时的间隔和更新频率,并且可以在倒计时结束之后立即执行指定的函数,从而实现更加精确和可控的倒计时效果。
  3. 实现拖拽效果。使用requestAnimationFrame可以在拖拽过程中不断更新元素的位置,并且可以控制拖拽的速度和加速度,从而实现更加自然和流畅的拖拽效果。
  4. 实现页面的渐变效果。使用requestAnimationFrame可以在一段时间内不断更新页面元素的样式,并且可以控制渐变的速度和方向,从而实现更加自然和流畅的渐变效果。

需要注意的是,requestAnimationFrame并不是万能的,它也有一些局限性。例如,在一些需要高精度计算的场景下,requestAnimationFrame可能无法满足需求,此时可能需要使用其他更加精细的计时器或者事件来实现。

实现平滑滚动效果

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Smooth Scroll</title>
    <style>
      body {
        height: 8000px;
      }
      .scroll-btn {
        position: fixed;
        bottom: 20px;
        right: 20px;
        cursor: pointer;
      }
    </style>
  </head>
  <body>
    <button class="scroll-btn">Scroll to Bottom</button>
    <script>
      const btn = document.querySelector('.scroll-btn');
      const scrollHeight = document.body.scrollHeight;
      const screenHeight = window.innerHeight;

      function scrollToBottom() {
        const currentScroll = window.pageYOffset;
        const remainingScroll = scrollHeight - currentScroll - screenHeight;

        if (remainingScroll > 0) {
          window.scrollTo(0, currentScroll + remainingScroll / 20);
          requestAnimationFrame(scrollToBottom);
        }
      }

      btn.addEventListener('click', scrollToBottom);
    </script>
  </body>
</html>

在这个示例中,我们首先获取了页面的滚动高度和屏幕高度。然后,定义了一个scrollToBottom函数,这个函数会在每一帧中更新滚动位置,并且使用requestAnimationFrame函数注册下一次动画帧。在scrollToBottom函数中,我们首先计算出当前滚动位置和还剩余的滚动距离,然后根据剩余滚动距离计算出滚动的距离,并使用window.scrollTo函数实现滚动。最后,如果还有剩余的滚动距离,就使用requestAnimationFrame函数注册下一次动画帧,从而实现平滑滚动效果。

实现倒计时效果

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Countdown</title>
    <style>
      body {
        height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
      }
      #countdown {
        font-size: 48px;
      }
    </style>
  </head>
  <body>
    <div id="countdown"></div>
    <script>
      const countdown = document.querySelector('#countdown');
      let endTime = Date.now() + 10000; // 倒计时 10s

      function updateCountdown() {
        const remainingTime = endTime - Date.now();

        if (remainingTime > 0) {
          const remainingSeconds = Math.floor(remainingTime / 1000);
          countdown.innerText = remainingSeconds;
          requestAnimationFrame(updateCountdown);
        } else {
          countdown.innerText = '0';
        }
      }
    //   updateCountdown()
      requestAnimationFrame(updateCountdown);
    </script>
  </body>
</html>

在这个示例中,我们首先定义了一个endTime变量,表示倒计时结束的时间。然后,定义了一个updateCountdown函数,这个函数会在每一帧中更新倒计时,并且使用requestAnimationFrame函数注册下一次动画帧。在updateCountdown函数中,我们首先计算出当前的剩余时间,然后根据剩余时间计算出剩余秒数,并更新倒计时的显示。最后,如果还有剩余时间,就使用requestAnimationFrame函数注册下一次动画帧,从而实现倒计时效果。

实现拖拽效果

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Drag and Drop</title>
    <style>
      #drag {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 100px;
        height: 100px;
        background-color: #f00;
        cursor: move;
      }
    </style>
  </head>
  <body>
    <div id="drag"></div>
    <script>
      const drag = document.querySelector('#drag');
      let isDragging = false;
      let startX = 0;
      let startY = 0;

      function handleMouseDown(event) {
        isDragging = true;
        startX = event.clientX;
        startY = event.clientY;
      }

      function handleMouseMove(event) {
        if (isDragging) {
          const deltaX = event.clientX - startX;
          const deltaY = event.clientY - startY;
          const currentX = parseInt(drag.style.left) || 0;
          const currentY = parseInt(drag.style.top) || 0;

          drag.style.left = currentX + deltaX + 'px';
          drag.style.top = currentY + deltaY + 'px';

          startX = event.clientX;
          startY = event.clientY;

          requestAnimationFrame(handleMouseMove);
        }
      }

      function handleMouseUp() {
        isDragging = false;
      }

      drag.addEventListener('mousedown', handleMouseDown);
      document.addEventListener('mousemove', handleMouseMove);
      document.addEventListener('mouseup', handleMouseUp);
    </script>
  </body>
</html>

在这个示例中,我们首先定义了一个可拖拽的元素drag,并且为它添加了鼠标样式和事件。然后,定义了三个函数handleMouseDownhandleMouseMovehandleMouseUp,分别处理鼠标按下、鼠标移动和鼠标抬起事件。在handleMouseDown函数中,我们记录下鼠标按下时的位置。在handleMouseMove函数中,我们首先判断鼠标是否按下,如果按下了,就计算出鼠标移动的距离,并根据距离更新元素的位置。最后,如果正在拖拽,就使用requestAnimationFrame函数注册下一次动画帧,从而实现平滑拖拽效果。在handleMouseUp函数中,我们记录下鼠标抬起的事件,表示拖拽结束。

实现渐变效果

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>渐变效果示例</title>
</head>
<body>
  <div id="myElement" style="width: 200px; height: 200px;"></div>

  <script>
    const element = document.getElementById('myElement'); // 获取需要渐变的元素
    let start = null; // 记录动画开始时间
    const duration = 10000; // 动画持续时间(单位:毫秒)
    const startColor = [255, 255, 255]; // 起始颜色(白色)
    const endColor = [0, 0, 0]; // 结束颜色(黑色)

    function animate(timestamp) {
      if (!start) start = timestamp; // 如果动画未开始,则记录开始时间
      const elapsed = timestamp - start; // 计算动画已经进行的时间
      const progress = Math.min(elapsed / duration, 1); // 计算动画进度(0-1)
      const color = interpolateColor(startColor, endColor, progress); // 计算当前颜色
      element.style.backgroundColor = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; // 设置元素背景颜色

      if (progress < 1) {
        requestAnimationFrame(animate); // 如果动画未结束,则继续执行动画
      }
    }

    requestAnimationFrame(animate); // 开始执行动画

    function interpolateColor(startColor, endColor, progress) {
      const color = [];
      for (let i = 0; i < 3; i++) {
        color[i] = Math.round(startColor[i] + (endColor[i] - startColor[i]) * progress);
      }
      return color;
    }
  </script>
</body>
</html>

Vue中结合使用

以下是一个简单的自定义指令示例,用于在元素滚动时实现类似于 Parallax 效果

<template>
  <div class="parallax" v-parallax="{ speed: 0.2 }">
    <img src="image.jpg">
  </div>
</template>

<script>
export default {
  directives: {
    parallax: {
      bind: function (el, binding) {
        const speed = binding.value.speed || 0.1;
        let lastScrollTop = 0;
        let animationFrameId = null;

        function animate() {
          const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
          const offset = (scrollTop - lastScrollTop) * speed;
          lastScrollTop = scrollTop;
          el.style.transform = `translateY(${offset}px)`;
          animationFrameId = requestAnimationFrame(animate);
        }

        animate();

        el.__parallax_unbind__ = function() {
          cancelAnimationFrame(animationFrameId);
        };
      },
      unbind: function (el) {
        el.__parallax_unbind__();
        delete el.__parallax_unbind__;
      }
    }
  }
};
</script>

<style scoped>
.parallax {
  position: relative;
  height: 300px;
  overflow: hidden;
}

.parallax img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: auto;
}
</style>

在这个例子中,我们创建了一个名为 v-parallax 的指令,并传递了一个 speed 参数用于控制滚动速度。在指令的 bind 钩子中,我们使用 requestAnimationFrame 来实现动画效果,根据滚动的位置计算出偏移量,并将其应用于元素的 transform 属性。

在指令的 unbind 钩子中,我们清除了动画效果,以避免在元素被销毁时仍然继续执行动画。

在模板中,我们将指令应用于包含图片的 div 元素上,并设置了一些基本的样式。当页面滚动时,图片将以不同的速度向上或向下滚动,实现类似于 Parallax 效果。

注意,在这个示例中,我们使用了 window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop 来获取滚动的位置。这是因为不同的浏览器可能会使用不同的属性来表示滚动偏移量。

另外,由于 requestAnimationFrame 在每秒钟执行大约 60 次,因此它比使用 setTimeoutsetInterval 更加高效和流畅。

React中结合使用

实现一个逐渐变暗的背景效果

import React, { useRef, useEffect } from 'react';

function Background() {
  const backgroundRef = useRef(null);

  useEffect(() => {
    let requestId;
    let opacity = 1;

    function animate() {
      opacity -= 0.01;
      if (opacity < 0) {
        opacity = 0;
      }
      backgroundRef.current.style.opacity = opacity;
      if (opacity > 0) {
        requestId = requestAnimationFrame(animate);
      }
    }

    requestId = requestAnimationFrame(animate);

    return () => {
      cancelAnimationFrame(requestId);
    };
  }, []);

  return (
    <div
      ref={backgroundRef}
      style={{
        position: 'fixed',
        top: 0,
        left: 0,
        width: '100%',
        height: '100%',
        backgroundColor: '#000',
        opacity: 1
      }}
    />
  );
}

在这个例子中,我们使用 useRef 钩子创建了一个名为 backgroundRef 的引用,并将其绑定到一个 div 元素上。在 useEffect 钩子中,我们使用 requestAnimationFrame 实现了逐渐变暗的背景效果。

useEffect 钩子中,我们使用了一个 requestId 变量来存储 requestAnimationFrame 的返回值,以便在组件被卸载时可以清除它。我们还使用了一个 opacity 变量来存储背景的透明度,并在每次 animate 函数调用中减少它。当透明度小于 0 时,我们将其设置为 0,并停止调用 requestAnimationFrame

在组件的返回值中,我们将 backgroundRef 绑定到一个 div 元素上,并设置了一些基本的样式。在 useEffect 钩子中,我们使用 backgroundRef 的 current属性来获取该元素,并将其透明度设置为opacity` 变量的值。在初次渲染时,背景的透明度将被设置为 1。

当组件被卸载时,我们使用 cancelAnimationFrame 函数清除 requestAnimationFrame 的调用。这是为了防止在组件卸载后仍然继续执行动画。

与setTimeout比较

requestAnimationFramesetTimeout 都是用于异步执行代码的 JavaScript API。

异同点:

  1. 执行时机:setTimeout 函数会在指定的时间间隔后执行回调函数,而 requestAnimationFrame 则会在浏览器下一次重绘之前执行回调函数。通常情况下,浏览器的重绘频率是每秒钟 60 次,因此 requestAnimationFrame 通常比 setTimeout 更加高效、流畅。
  2. 回调函数参数:setTimeout 函数的回调函数会接收一个可选的参数,表示在指定的时间间隔后是否重复执行回调函数。而 requestAnimationFrame 的回调函数不接受任何参数。
  3. 取消执行:通过调用 clearTimeout 函数可以取消 setTimeout 的执行,而通过调用 cancelAnimationFrame 函数可以取消 requestAnimationFrame 的执行。
  4. 兼容性:setTimeout 是一个比较老的 API,在所有主流浏览器中都得到了支持。而 requestAnimationFrame 则是比较新的 API,在一些旧的浏览器中可能不被支持。

异同点总结:

相同点:都是用于异步执行代码的 JavaScript API。

不同点:

  1. 执行时机不同,setTimeout 在指定时间后执行回调函数,requestAnimationFrame 在浏览器下一次重绘前执行回调函数。

  2. 回调函数参数不同,setTimeout 的回调函数接受一个可选的参数,表示是否重复执行回调函数,requestAnimationFrame 不接受任何参数。

  3. 取消执行方式不同,clearTimeout 取消 setTimeout 的执行,cancelAnimationFrame 取消 requestAnimationFrame 的执行。

  4. 兼容性不同,setTimeout 在所有主流浏览器中都得到了支持,requestAnimationFrame 在一些旧的浏览器中可能不被支持。

    另外,需要注意的是,setTimeoutrequestAnimationFrame 适用于不同的场景。setTimeout 通常用于延迟执行一段代码,例如实现一个倒计时功能。而 requestAnimationFrame 则通常用于实现动画效果,因为它能够根据浏览器的刷新率自动调整动画的帧率,从而使得动画更加流畅。

与setInterval比较

异同点:

  1. 执行时机:setInterval 函数会在指定的时间间隔后重复执行回调函数,而 requestAnimationFrame 则会在浏览器下一次重绘之前执行回调函数。与 setTimeout 一样,requestAnimationFrame 通常比 setInterval 更加高效、流畅。
  2. 回调函数参数:setInterval 函数的回调函数会接收一个可选的参数,表示在指定的时间间隔后是否重复执行回调函数。而 requestAnimationFrame 的回调函数不接受任何参数。
  3. 取消执行:通过调用 clearInterval 函数可以取消 setInterval 的执行,而通过调用 cancelAnimationFrame 函数可以取消 requestAnimationFrame 的执行。
  4. 执行间隔:setInterval 函数的执行间隔是固定的,而 requestAnimationFrame 的执行间隔则会根据浏览器的刷新率自动调整。因此,requestAnimationFrame 更加适合实现动画效果,而 setInterval 更适合实现一些周期性的操作,例如定时发送心跳包等。

异同点总结:

相同点:都是用于周期性地执行代码的 JavaScript API。

不同点:

  1. 执行时机不同,setInterval 在指定时间间隔后重复执行回调函数,requestAnimationFrame 在浏览器下一次重绘前执行回调函数。

  2. 回调函数参数不同,setInterval 的回调函数接受一个可选的参数,表示是否重复执行回调函数,requestAnimationFrame 不接受任何参数。

  3. 取消执行方式不同,clearInterval 取消 setInterval 的执行,cancelAnimationFrame 取消 requestAnimationFrame 的执行。

  4. 执行间隔不同,setInterval 的执行间隔是固定的,而 requestAnimationFrame 的执行间隔则会根据浏览器的刷新率自动调整。

    另外,需要注意的是,setIntervalrequestAnimationFrame 适用于不同的场景。setInterval 通常用于定时执行一些简单的周期性操作,例如每隔一定时间打印一次日志。而 requestAnimationFrame 则通常用于实现动画效果,因为它能够根据浏览器的刷新率自动调整动画的帧率,从而使得动画更加流畅。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值