说说requestAnimationFrame吧

2,723 阅读4分钟

Hello,这里是mouche,当然你也可以叫我某车,反正大家都爱这么叫😁

一、了解浏览器

  • 一般的浏览器的刷新率为60Hz,也就是说,1秒钟就会刷新60次,也就是说,大概每过16.6ms浏览器会渲染一帧画面
  • 速度这么快,也就是我们为什么感受不到浏览器数据在刷新的原因,而我们很多动画的效果就是想要这样连贯的,平滑的方式进行过渡。也就是说我们想要它也跟浏览器一样快得我们感受不到它是一帧一帧变化的,而是连贯的
  • 所以我们一般希望动画间隔时间为16.6ms
    • 为什么不更快呢:更快一点也是一样的效果为什么要消耗这个资源去做无意义的事
    • 为什么不更慢呢:比这个时间慢呢,就容易出现掉帧现象

二、实现动画的方式

  • 当然,在学新的东西的时候也不要忘记老本,那么我们就先尝试用以下方式分别实现如下动画,注:以下仅为个人回顾实现不是最佳实现(😗)
  • bg为背景盒子类名,myBox为滑动盒子类名

🍖CSS实现:transition

  • transition主要就是易用,实现代码就两行哈哈,但是它的局限性也是很大的。它本身没法在网页加载时自动发生,需要事件触发,其次它是一次性除非再次触发不然不能重复发生,再者它不能定义中间状态,而且一条语句也只能只能定义一个属性的变化
.bg:hover .myBox{
    left: 400px;
    transition:  left 6s linear 0s;
  }

🍖CSS实现:animation

  • 相比而言,animation就解决了上述transition的局限,它可以自动发生,也可以定义中间状态
  • 在动画函数设置起始状态和结束状态,然后设置animation属性即可,这里设置的是在6s匀速滑动完成,
/* 使用transition */
/* 使用animation */
.myBox{
  animation: go 6s linear;
}
 @keyframes go {
    from {transform: translateX(0px)}
    to {transform: translateX(400px)}
  }

🍖JavaScript实现:setInterval

//使用setInterval
  const myBox = document.querySelector('.myBox');
  let myBoxLeft = 0;
  const animation = setInterval(()=>{
    myBoxLeft+=1;
    myBox.style.left = myBoxLeft + 'px';
    if(myBox.style.left == '400px'){
     clearInterval(animation);
    }
  },16.6)

🍖JavaScript实现:setTimeout

//使用setTimeout
  const myBox = document.querySelector('.myBox');
  let myBoxLeft = 0;
  const animation = () => {
    myBoxLeft+=1;
    myBox.style.left = myBoxLeft + 'px';
    if(myBoxLeft<=400) {
      setTimeout(animation,16.6);
    }
  }
  animation()

🍖CSS动画和JS动画的比较

拓展+给后面铺垫一下

  • 从上面的实现可以知道CSS动画我们是定义不同时间的状态,然后它进行补间动画,而JS实现为了保证其过程流畅,是帧动画
  • 对于帧速表现不好的低版本浏览器,CSS动画可以做到自然降级,而 JS 则需要撰写额外代码
  • CSS能实现的效果比较有效,例如无法实现贝塞尔曲线,相对来说,JS动画在一定程度上可以实现更加复杂的动画效果

对于一些博文所提到的CSS动画可以进行硬件加速,但是我觉得JS同样也可以用一个 3D 特性的触发器(比如translate3d())来让浏览器为这个元素开辟一个 GPU 层,无论是CSS动画还是JS动画都可以是受益者

三、requestAnimationFrame登场

我们采用三步走来了解了解它

🥩是什么

  • requestAnimationFrame翻译过来就是请求动画帧,那他肯定是搞动画的喽,而且还是帧动画
  • 它告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画

🥩怎么用

  • 使用方法基本和setTimeout一样,但是不需要设置时间间隔,只需要传入一个回调函数
  • 还是上面那个动画效果,我们直接使用setTimeout的代码进行更改
  //使用requestAnimationFrame
  const myBox = document.querySelector('.myBox');
  let myBoxLeft = 0;
  const animation = () => {
    myBoxLeft+=1;
    myBox.style.left = myBoxLeft + 'px';
    if(myBoxLeft<=400) {
      // setTimeout(animation,16.6);
      requestAnimationFrame(animation);
    }
  }
  animation()
  • 返回值:一个整数,请求 ID ,是回调列表中唯一的标识。干啥用呢,就清空呗,难不成这个动画还不让停了( Bushi
window.cancelAnimationFrame()

🥩为什么

  • 作用: 代替定时器做更加流畅高性能的动画,解决了定时器动画时间间隔不问题的问题

    我们上面说到了它是帧动画,那么帧动画肯定就要考虑帧间隔问题

    • 为什么说定时器动画间隔时间不稳定
      • 假设显示器刷新率为60Hz,为了让动画流畅,需要将setTimeout时间间隔定位为1000/60。但是因为定时器为宏任务,必须等同步任务执行完成,以及等微任务执行完成才会执行其中的回调----导致规定的间隔实际上是不稳定的不准确的
    • 为什么说requestAnimationFrame解决了这个问题
      • requestAniamationFrame的执行步伐是跟着系统走的,如果系统的绘制频率是60Hz,那么回调函数就每16.7ms被执行一次--这样不会就引起丢帧现象

🥩优点:

  • requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率
  • 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量