深入理解HTML5定时器requestAnimationFrame的使用


Posted in HTML / CSS onDecember 12, 2018

前言

计时器一直是javascript动画的核心技术。而编写动画循环的关键是要知道延迟时间多长合适。一方面,循环间隔必须足够短,这样才能让不同的动画效果显得平滑流畅;另一方面,循环间隔还要足够长,这样才能确保浏览器有能力渲染产生的变化。

大多数电脑显示器的刷新频率是60Hz,大概相当于每秒钟重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是1000ms/60,约等于16.6ms。

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

requestAnimationFrame采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。

特点

  • requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率。
  • 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量。
  • requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销。

用法

requestAnimationFrame方法将一个callback作为参数,回调函数会被传入一个参数,DOMHighResTimeStamp,指示当前被 requestAnimationFrame() 排序的回调函数被触发的时间。返回值是一个请求 ID,表示回调列表中唯一的标识。可以传这个值给 window.cancelAnimationFrame() 以取消回调函数。

requestID = window.requestAnimationFrame(callback);

利用这个api,可以将某些代码放到下一次重新渲染时执行,避免短时间内触发大量reflow。

例如页面滚动事件(scroll)的回调函数就很适合使用这个api, 将回调操作推迟到下一次重新渲染。但需要注意的是requestAnimationFrame 不管理回调函数,即在回调被执行前,多次调用带有同一回调函数的 requestAnimationFrame,会导致回调在同一帧中执行多次。最简单的,可以用节流函数来解决这个问题,也可以想办法让requestAnimationFrame的队列里同样的回调函数只有一个:

let scheduledAnimationFrame = false;
document.body.onscroll = () => {
    if (scheduledAnimationFrame) return;
    scheduledAnimationFrame = true;
    window.requestAnimationFrame(() => {
        scheduledAnimationFrame = false;
        // do something
    });
};

当然,最佳的应用场景还是在帧动画里,可以大幅优化性能。

面试题

如何渲染几万条数据并不卡住界面

这道题考察了如何在不卡住页面的情况下渲染数据,也就是说不能一次性将几万条都渲染出来,而应该一次渲染部分 DOM,那么就可以通过 requestAnimationFrame 来每 16 ms 刷新一次。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <ul>控件</ul>
  <script>
    setTimeout(() => {
      // 插入十万条数据
      const total = 100000
      // 一次插入 20 条,如果觉得性能不好就减少
      const once = 20
      // 渲染数据总共需要几次
      const loopCount = total / once
      let countOfRender = 0
      let ul = document.querySelector("ul");
      function add() {
        // 优化性能,插入不会造成回流
        const fragment = document.createDocumentFragment();
        for (let i = 0; i < once; i++) {
          const li = document.createElement("li");
          li.innerText = Math.floor(Math.random() * total);
          fragment.appendChild(li);
        }
        ul.appendChild(fragment);
        countOfRender += 1;
        loop();
      }
      function loop() {
        if (countOfRender < loopCount) {
          window.requestAnimationFrame(add);
        }
      }
      loop();
    }, 0);
  </script>
</body>
</html>

兼容性

一些老的浏览器是不支持这个api的,为了也能使用这个api, 可以自定义这个方法,挂载到window下面:

(function() {
    var lastTime = 0;
    var vendors = ['webkit', 'moz'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame =
          window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
    }

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); },
              timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };

    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

HTML / CSS 相关文章推荐
IE矩阵Matrix滤镜旋转与缩放及如何结合transform
Nov 29 HTML / CSS
使用CSS3来绘制一个月食图案
Jul 18 HTML / CSS
CSS的background属性及CSS3的背景图片设置总结
Jun 13 HTML / CSS
通过一张图教会你CSS3倒影的实现
Sep 26 HTML / CSS
CSS3实现文本垂直排列的方法
Jul 10 HTML / CSS
详解H5本地储存Web Storage
Jul 03 HTML / CSS
HTML5 和小程序实现拍照图片旋转、压缩和上传功能
Oct 08 HTML / CSS
HTML5验证以及日期显示的实现详解
Jul 05 HTML / CSS
Html5自定义字体解决方法
Oct 09 HTML / CSS
DIV CSS实现网页背景半透明效果
Dec 06 HTML / CSS
CSS浮动引起的高度塌陷问题
Aug 05 HTML / CSS
使用CSS实现按钮边缘跑马灯动画
May 07 HTML / CSS
手对手的教你用canvas画一个简单的海报的方法示例
Dec 10 #HTML / CSS
html5+css如何实现中间大两头小的轮播效果
Dec 06 #HTML / CSS
Html5 Canvas动画基础碰撞检测的实现
Dec 06 #HTML / CSS
canvas压缩图片以及卡片制作的方法示例
Dec 04 #HTML / CSS
canvas探照灯效果的示例代码
Nov 30 #HTML / CSS
localStorage的过期时间设置的方法详解
Nov 26 #HTML / CSS
canvas拼图功能实现代码示例
Nov 21 #HTML / CSS
You might like
php实现ip白名单黑名单功能
2015/03/12 PHP
PHP调用存储过程返回值不一致问题的解决方法分析
2016/04/26 PHP
[原创]php实现子字符串位置相互对调互换的方法
2016/06/02 PHP
laravel 框架执行流程与原理简单分析
2020/02/01 PHP
Javascript 面向对象 继承
2010/05/13 Javascript
web的各种前端打印方法之jquery打印插件jqprint实现网页打印
2013/01/09 Javascript
JS测试显示屏分辨率以及屏幕尺寸的方法
2013/11/22 Javascript
js中的onchange和onpropertychange (onchange无效的解决方法)
2014/03/08 Javascript
JQuery中基础过滤选择器用法实例分析
2015/05/18 Javascript
text-align:justify实现文本两端对齐 兼容IE
2015/08/19 Javascript
js和C# 时间日期格式转换的简单实例
2016/05/28 Javascript
基于jQuery实现仿微博发布框字数提示
2016/07/27 Javascript
实现两个文本框同时输入的实例
2017/09/25 Javascript
浅谈Redux中间件的实践
2018/07/27 Javascript
详解如何在webpack中做预渲染降低首屏空白时间
2018/08/22 Javascript
如何使用50行javaScript代码实现简单版的call,apply,bind
2019/08/14 Javascript
node中使用log4js4.x版本记录日志的方法
2019/08/20 Javascript
在微信小程序中渲染HTML内容3种解决方案及分析与问题解决
2020/01/12 Javascript
JavaScript实现瀑布流布局的3种方式
2020/12/27 Javascript
Python正则表达式非贪婪、多行匹配功能示例
2017/08/08 Python
Python中实现变量赋值传递时的引用和拷贝方法
2018/04/29 Python
python实现图片,视频人脸识别(opencv版)
2020/11/18 Python
scrapy处理python爬虫调度详解
2020/11/23 Python
eBay德国站:eBay.de
2017/09/14 全球购物
绢花、人造花和人造花卉:BLOOM
2019/08/07 全球购物
美国在线肉类和海鲜配送:Crowd Cow
2020/10/02 全球购物
亚马逊海外购:亚马逊美国、英国、日本、德国直邮
2021/03/18 全球购物
如何写出高质量、高性能的MySQL查询
2014/11/17 面试题
创业计划书中包含的9个方面
2013/12/26 职场文书
亮化工程实施方案
2014/03/17 职场文书
大学活动总结模板
2014/07/10 职场文书
大专护理专业自荐信
2015/03/25 职场文书
道歉的话语大全
2015/05/12 职场文书
获奖感言一句话
2015/07/31 职场文书
解决python存数据库速度太慢的问题
2021/04/23 Python
java中为什么说子类的构造方法默认访问的是父类的无参构造方法
2022/04/13 Java/Android