requestAnimationFrame,终结定时器动画时代!

7,852 阅读7分钟

前言

风和日丽,饶有兴致,翻开之前写的一个简单的动画插件,发现是用定时器写的,但是作为有追求的前端,一个问题怎么能有一种解决方案呢?故而,遍寻资料,终于看见曙光,让我查到了requestAnimationFrame 这个宿主对象的方法,也能能优雅的实现js动画!

传统动画实现

在我们前端的传统中,在古老的ie称霸的年代,我们想要实现动画,必须要借助setTimeout或setInterval这两个函数,下面我们来思考一个问题:

我们让一个数字从0开始逐渐变大,到达100时在逐渐变小,如此往复

那么,传统的定时器的写法应该怎么写呢?废话少说上代码

//css部分
<style>
      #a {
        font-size: 30px;
      }
    </style>
//html部分
<div id="a"></div>
//js部分
 var e = document.getElementById("a");
      var flag = true;
      var left = 0;

      function render() {
        if (flag == true) {
          if (left >= 100) {
            flag = false;
          }
          e.innerHTML = left++;
        } else {
          if (left <= 0) {
            flag = true;
          }
          e.innerHTML = left--;
        }
      }
      setInterval(function () {
        render();
      }, 1000/60);

以上写法便可以实现循环往复的变大变小的操作!

这种方法,可行吗?当然可行,完美吗?也还算完美,当突然发现新大陆以后,定时器便彻底被终结了,就比如,你用了苹果的Retina屏幕以后,发现再也回不去了是一个道理,你说1080p的屏幕完美吗?挺完美的,然而当你拿到Retina以后,直呼真香!

window.requestAnimationFrame

在了解requestAnimationFrame之前,我们先来了解几个概念,阐述一下为啥requestAnimationFrame真香

什么是屏幕刷新率

之所以我们能看到动画,一些动画效果,完全时由我们的显示器在短时间内不断播放一张张图片,当播放速率过快时,便形成了动画效果,而我们的显示器在播放图片时,一般有一个播放的频率标准,我们叫做屏幕刷新率,即图像在屏幕上更新的速度,也即屏幕上的图像每秒钟出现的次数,它的单位是赫兹(Hz)。一般情况下,当刷新率达到60hz基本我们的肉眼就感觉不到他是静态的了,变成了一个连贯的动画!

那你可知这是为什么呢?

为什么你感觉不到这个变化? 那是因为人的眼睛有视觉停留效应,即前一副画面留在大脑的印象还没消失,紧接着后一副画面就跟上来了,这中间只间隔了16.7ms(1000/60≈16.7), 所以会让你误以为屏幕上的图像是静止不动的。而屏幕给你的这种感觉是对的,试想一下,如果刷新频率变成1次/秒,屏幕上的图像就会出现严重的闪烁,这样就很容易引起眼睛疲劳、酸痛和头晕目眩等症状。(跟主题没啥关系,强行科普一波)

动画原理

由于高刷新率的存在,加上人眼睛的视觉停留效应,理解动画的原理就变得非常简单了。 画本质就是要让人眼看到图像被刷新而引起变化的视觉效果,这个变化要以连贯的、平滑的方式进行过渡。,如果一来,在我们的浏览器,中就能看到连贯的动画效果

定时器的缺点

上面的讲述你应该已经大概知道定时器能实现动画效果了,其实他就是通过不断改变这个元素的位置或者值,来达到快速播放静图片的效果,从而形成一个完整的动画

然而由于定时器的在js中的执行方式,导致它有一些小小的瑕疵,虽然可以忍受,但是有更好的东西出来,为啥不淘汰掉他呢?

我们知道定时器的执行时间并不是确定的。这是由于js是个单线程的语言,他必须使用异步,来解决一些需要延时执行这个问题,那么为什么说定时器的执行时间不是确定的呢?那就得来细数一下轮询了

Event Loop

Event Loop的是计算机系统的一种运行机制。JavaScript语言就采用这种机制,来解决单线程运行带来的一些问题。

在JavaScript中,任务被分为两种,一种宏任务(MacroTask)也叫Task,一种叫微任务(MicroTask)。

常见的宏任务有:script全部代码、setTimeout、setInterval、setImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN)、I/O、UI Rendering。

常见的微任务有:Process.nextTick(Node独有)、Promise、Object.observe(废弃)、MutationObserver

下面来简单学习一下Event Loop的执行过程

首先Javascript 有一个 main thread 主线程和 call-stack 调用栈(执行栈),所有的任务都会被放到调用栈等待主线程执行。 JS调用栈采用的是后进先出的规则,当函数执行的时候,会被添加到栈的顶部,当执行栈执行完成后,就会从栈顶移出,直到栈内被清空。 然后,当执行宏任务时,遇见定时器,那么便给定时器中的内容压入队列中,到下一次的Event Loop执行,接着去执行,微任务

最后,微任务执行完毕,清空执行栈,拿到队列中的下一次Event Loop的内容,在开始执行,走到这里,你会发现,在定时器执行的时候,前面还有会一堆同步代码也需要时间,如果前面有个循环个三五百次的话,会非常浪费时间,这就暴露出了定时器的一个缺点:丢帧现象,就是每次间隔其实是不确定的,导致跟浏览器的刷刷新率匹配不上,有可能出现的丢帧现象!(后经过大佬更正,定时器丢帧的原因仅仅是没有被浏览器的策略干涉,并不是会被同步任务阻塞)

   //这段代码可以证实
  requestAnimationFrame(() => console.log("Hello World"));
      while (true);
   

看完流程以后,请仔细参悟上图,会有收获的!

requestAnimationFrame是个啥?

requestAnimationFrame是html5 提供的一个专门用于请求动画的API,顾名思义就是请求动画帧,他被封装在宿主对象中, window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

requestAnimationFrame的优势是啥?

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

那requestAnimationFrame怎么使用呢?

//html
  <div id="a"></div>
  //js
    <script>
      var e = document.getElementById("a");
      var flag = true;
      var left = 0;

      function render() {
        if (flag == true) {
          if (left >= 100) {
            flag = false;
          }
          e.innerHTML = left++;
        } else {
          if (left <= 0) {
            flag = true;
          }
          e.innerHTML = left--;
        }
      }
      // setInterval(function () {
      //   render();
      // }, 1000);
      (function animloop() {
        render();
        var a=window.requestAnimationFrame(animloop);
      })();

到这里就可以结束了,但是突然有冒出来个疑问,他怎么停止呢?

执行函数放回一个id是回调列表中唯一的标识。是个非零值,没别的意义。你可以传这个值给 window.cancelAnimationFrame() 以取消回调函数。

window.cancelAnimationFrame(a)//注意这个要写在函数体内部,

总结

到这里,基本就结束了requestAnimationFrame的探索,由于我也是边学边写,不对之处,请大佬指正!

鸣谢巨人: 深入理解 requestAnimationFrame requestAnimationFrame详解