全面理解 requestAnimationFrame

2023-06-14
21178 分钟
...

一、requestAnimationFrame 的定义

官方的定义:

requestAnimationFrame 即请求动画帧,它是一个浏览器的宏任务。简单的说,这个api主要是用来做动画的。

二、关于前端动画的两个问题

1. 前端动画方案有哪些?

  • css动画

    • transition:过渡动画
    • animation:直接动画(搭配@keyframes
  • js动画

    • setIntervalsetTimeout定时器(比如不停地更改dom元素的位置,使其运动起来)
    • canvas动画,搭配js中的定时器去运动起来(canvas只是一个画笔,然后我们通过定时器会使用这个画笔去画画-动画)
    • requestAnimationFrame动画(js动画中的较好方案)

2. 为何要使用这个新的api来做动画?

在工作中,做动画最优的方案无疑是css动画,但是某些特定场景下,css动画无法实现我们所需要的需求。这时,我们就要考虑使用js去做动画了,canvas动画本质也是定时器动画。使用定时器动画干活,实际上是可以的,但是存在一个最大的问题,就是动画会抖动,体验效果不是非常好。

而使用requestAnimationFrame去做动画,就不会出现抖动的现象。

这里笔者写一个demo动画(分别是上述两种方式实现dom元素向右平移)给大家看一下,就知道具体的区别。我们先看一下效果图:

由于gif录制软件的问题,看着都有点卡。实际上,大家把下方代码复制一份跑起来看的话,会发现定时器动画在微微颤抖,而requestAnimationFrame动画却稳如老狗

<!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>requestAnimationFrame_yyds</title>
    <style>
      body {
        box-sizing: border-box;
        background-color: #ccc;
      }

      .box1,
      .box2 {
        position: absolute;
        width: 160px;
        height: 160px;
        line-height: 160px;
        text-align: center;
        color: #fff;
        font-size: 13px;
      }

      .box1 {
        top: 40px;
        background: red;
      }

      .box2 {
        top: 210px;
        background: green;
      }
    </style>
  </head>

  <body>
    <button class="btn">👉 let's go!</button>
    <div class="box1">定时器动画</div>
    <div class="box2">请求动画帧</div>
    <script>
      // 动画思路:不断修改dom元素的left值,使其运动起来(动画)
      let box1 = document.querySelector('.box1')
      let box2 = document.querySelector('.box2')

      // setInterval定时器方式
      function setIntervalFn(

      ) {
        let timer = null
        box1.style.left = '0px'
        timer = setInterval(() => {
          let leftVal = parseInt(box1.style.left)
          if (leftVal >= 720) {
            clearInterval(timer)
          } else {
            box1.style.left = leftVal + 1 + 'px'
          }
        }, 17)
      }

      // requestAnimationFrame请求动画帧方式
      function requestAnimationFrameFn(

      ) {
        let timer = null // 可注掉
        box2.style.left = '0px'
        function callbackFn(

        ) {
          let leftVal = parseInt(box2.style.left)
          if (leftVal >= 720) {
            // 不再继续递归调用即可,就不会继续执行了,下面这个加不加都无所谓,因为影响不到
            // cancelAnimationFrame取消请求动画帧,用的极少,看下,下文中的回到顶部组件
            // 大家会发现并没有使用到这个api(这样写只是和clearInterval做一个对比)
            // 毕竟,正常情况下,requestAnimationFrame会自动停下来
            cancelAnimationFrame(timer) // 可注掉(很少用到)
          } else {
            box2.style.left = leftVal + 1 + 'px'
            window.requestAnimationFrame(callbackFn)
          }
        }
        window.requestAnimationFrame(callbackFn)
      }

      // 动画绑定
      let btn = document.querySelector('.btn')
      btn.addEventListener('click', () => {
        setIntervalFn()
        requestAnimationFrameFn()
      })
    </script>
  </body>

</html>

通过上述的例子,我们可以回答这个问题了:

  • 面试官问:requestAnimationFrame比定时器好在哪里?
  • 候选人答:好在比较稳定,动画不卡顿
  • 面试官说:你回去等通知吧...

所以在这里,我们还要顺带延伸一下,为什么定时器会卡,而requestAnimationFrame不会卡。在说这个问题之前,我们先来看下requestAnimationFrame的语法规则。

三、requestAnimationFrame的语法规则

requestAnimationFramejs中的setTimeout定时器函数基本一致,不过setTimeout可以自由设置间隔时间,而requestAnimationFrame的间隔时间是由浏览器自身决定的,大约是17毫秒左右

1.requestAnimationFrame我们可以在控制台输入window,然后展开查看其身上的属性,就能找到了,如下图:

xxx.png

xxx.png

2.由上图我们可以看到,requestAnimationFrame本质上是一个全局window对象上的一个属性函数,所以我们使用时,直接:window.requestAnimationFrame(callBack)即可。

3.和定时器一样其接收的参数callback也是一个函数,即下一次重绘之前更新动画帧所调用的函数,即在这个函数体中,我们可以写对应的逻辑代码(和定时器类似)。

4.requestAnimationFrame也有返回值,返回值是一个整数,主要是定时器的身份证标识,可以使用 window.cancelAnimationFrame()来取消回调函数执行,相当于定时器中的clearTimeout()

5.二者也都是只执行一次,想要继续执行,做到类似setInterval的效果,需要写成递归的形式(上述案例中也提到了)

四、关于卡顿的问题

1. 为什么定时器会卡

  • 我们在手机或者电脑显示屏上看东西时,显示屏会默默的不停地干活(刷新画面)
  • 这个刷新值得是每秒钟刷新次数,普通显示器的刷新率约为60Hz(每秒刷新60次),高档的有75Hz、90Hz、120Hz、144Hz等等
  • 刷新率次数越高,显示器显示的图像越清晰、越流畅、越丝滑
  • 不刷新就是静态的画面,刷新比较低就是卡了PPT的感觉
  • 动画想要丝滑流畅,需要卡住时间点进行代码操作(代码语句赋值、浏览器重绘)
  • 所以只需要每隔1000毫秒的60分之一(60HZ)即约为17毫秒,进行一次动画操作即可
  • 只要卡住这个17毫秒,每隔17毫秒进行操作,就能确保动画丝滑
  • 但是定时器的回调函数,会受到js的事件队列宏任务、微任务影响,可能设定的是17毫秒执行一次,但是实际上这次是17毫秒、下次21毫秒、再下次13毫秒执行,所以并不是严格的卡住了这个60HZ的时间
  • 没有在合适的时间点操作,就会出现:类似这样的情况:不变不变不变...
  • 于是就出现了,绘制不及时的情况,就会有抖动的出现(以上述案例,位置和时间没有线性对应更新变化导致看起来抖动)

2. 为何requestAnimationFrame不会卡

setTimeoutsetInterval的问题是,它们都不精确。它们的内在运行机制决定了时间间隔,参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行。

requestAnimationFrame能够做到,精准严格的卡住显示器刷新的时间,比如普通显示器60HZ它会自动对应17ms执行一次,高级显示器120HZ,它会自动对应9ms执行一次。当然requestAnimationFrame只会执行一次,想要使其多次执行,要写成递归的形式。

所以,这就是requestAnimationFrame的好处,window.requestAnimationFrame这个api就是解决了定时器不精准的问题的。

五、requestAnimationFrame 的应用场景

比如:回到顶部组件,就是使用requestAnimationFrame实现的。

效果图:

代码:

<template>
  <transition name="fade-transform">
    <div
         v-show="visible"
         class="backWrap"
         :style="{
                 bottom: bottom + 'px',
                 right: right + 'px',
                 }"
         @click="goToTop"
         >
      <slot></slot>
    </div>
  </transition>
</template>

<script>

  export default {
    name: "myBack",
    props: {
      bottom: {
        type: Number,
        default: 72,
      },
      right: {
        type: Number,
        default: 72,
      },
      // 回到顶部出现的滚动高度位置
      showHeight: {
        type: Number,
        default: 240,
      },
      // 拥有滚动条的那个dom元素的id或者class,用于下方选中操作更改滚动条滚动距离
      scrollBarDom: String,
    },
    data(

    ) {
      return {
        visible: false,
        scrollDom: null,
      };
    },
    mounted(

    ) {
      if (document.querySelector(this.scrollBarDom)) {
        this.scrollDom = document.querySelector(this.scrollBarDom);
        // 不用给window绑定监听滚动事件,给对应滚动条元素绑定即可
        this.scrollDom.addEventListener("scroll", this.isShowGoToTop, true);
      }
    },
    beforeDestroy(

    ) {
      // 最后要解除监听滚动事件
      this.scrollDom.removeEventListener("scroll", this.isShowGoToTop, true);
    },
    methods: {
      isShowGoToTop(

      ) {
        // 获取滚动的元素,即有滚动条的那个元素
        if (this.scrollDom.scrollTop > 20) {
          this.visible = true;
        } else {
          this.visible = false;
        }
      },
      goToTop(

      ) {
        // 获取滚动的元素,即有滚动条的那个元素
        let scrollDom = document.querySelector(this.scrollBarDom);
        // 获取垂直滚动的距离,看看滚动了多少了,然后不断地修改滚动距离直至为0
        let scrollDistance = scrollDom.scrollTop;

        /**
 * window.requestAnimationFrame兼容性已经可以了,正常都有的
 * */
        if (window.requestAnimationFrame) {
          let fun = (

          ) => {
            scrollDom.scrollTop = scrollDistance -= 36;
            if (scrollDistance > 0) {
              window.requestAnimationFrame(fun); // 只执行一次,想多次执行需要再调用
            } else {
              scrollDom.scrollTop = 0;
            }
          };
          window.requestAnimationFrame(fun);
          return;
        }

        /**
 * 没有requestAnimationFrame的话,就用定时器去更改滚动条距离,使之滚动
 * */
        let timer2 = setInterval(() => {
          scrollDom.scrollTop = scrollDistance -= 36;
          if (scrollDistance <= 0) {
            clearInterval(timer2);
            scrollDom.scrollTop = 0;
          }
        }, 17);
      },
    },
  };
</script>

<style lang='less' scoped>

  .backWrap {
    position: fixed;
    cursor: pointer;
    width: 42px;
    height: 42px;
    background: #9cc2e5;
    border-radius: 4px;
    display: flex;
    justify-content: center;
    align-items: center;
    transition: all 0.5s;
  }

  // 过渡效果
  .fade-transform-leave-active,
  .fade-transform-enter-active {
    transition: all 0.36s;
  }

  .fade-transform-enter {
    opacity: 0;
    transform: translateY(-5px);
  }
  .fade-transform-leave-to {
    opacity: 0;
    transform: translateY(5px);
  }
</style>

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
css2025-03-04
CSS 实现文本省略显示【单行,2行,多行】
在 中,长文本内容可能会超出其容器的范围。为了保持良好的用户体验,我们通常会将超出的文本部分以省略号显示。本文将详细介绍如何在CSS中限制文本显示一行、两行或多行,超过部分显示省略号。 1\. 前言 限制文本显示长度并以省略号表示超出部分是常见的需求。通过CSS,可以简单地实现这一效果。我们将分别介绍单行文本、省略两行文本和多行文本的实现方法。 2\. 单行文本省略 要实现单行文本省略,主要使用以下CSS属性: : 禁止文本换行。 :...
学习面试
css2025-03-04
css中,有哪些方式可以隐藏页面元素?区别?
一、前言 在平常的样式排版中,我们经常遇到将某个模块隐藏的场景 通过 隐藏元素的方法有很多种,它们看起来实现的效果是一致的 但实际上每一种方法都有一丝轻微的不同,这些不同决定了在一些特定场合下使用哪一种方法 二、实现方式 通过 实现隐藏元素方法有如下: display:none visibility:hidden opacity:0 设置height、width模型属性为0 position:absolute clip path dis...
学习面试
css2025-03-04
css实现正方形的几种方案及优缺点
前言 在电商、个人博客等网站我们或多或少都可以看到css正方形的应用场景,自适应正方形布局是必须要掌握的;接下来采黎带大家揭开css自适应正方形神秘的面纱 正方形 这很简单,设置盒子宽高都为200px就可以了 自适应正方形 青铜 提起自适应,想必都会想到 rem 、 vw 等相对单位,我们分别用这些单位来实现一下。 rem 屏幕宽度大于1200px时,字体为16px,rem的盒子的大小为 160px \ 160px; 屏幕宽度小于120...
学习面试
css2025-03-04
Flexbox 弹性布局
一、是什么 简称 box,意为”弹性布局”,是一种一维的布局模型。一次只能处理一个维度上的元素布局,一行或者一列。 采用Flex布局的元素,称为 容器( ),简称”容器“;它的所有子元素自动成为容器成员,称为 项目( ),简称”项目“。 容器中默认存在两条轴, 主轴 和 交叉轴 。项目默认沿主轴排列,通过 来决定主轴的方向。 每根轴都有起始线和终止线,用来描述flexbox元素的方向。 任何一个容器都可以指定为 布局。设为 Flex 布...
学习面试
css2025-03-04
提升CSS渲染性能
1\. 提升CSS渲染性能: 2\. 合适使用css选择器: 3\. 提升css文件加载的性能: 4\. 精简css代码: 5\. 合理使用web fonts: 6\. css动画优化:
学习面试
css2024-08-29
CSS 终于在 2024 年增加了垂直居中功能
在 2024 年的 CSS 原生属性中允许使用 1 个 CSS 属性 进行垂直居中。 支持情况: | Chrome: 123 | Firefox: 125 | Safari: 17.4 | | | | | CSS 对齐一般会使用 或 布局,因为 在默认的流式布局中不起作用。在 2024 年,浏览器实现了 。 你不需要 flexbox 或 grid,只需要 1 个 CSS 属性就可以进行对齐。 因此内容不需要包裹在 div 中。 令人惊讶...
学习面试

评论

请登录后发表评论

去登录
加载评论中...

目录