在实际项目实践中,甲方爸爸提出了一个需求,实时传输Chrome浏览器的刷新帧率(FPS)至性能监控模块,于是各种搜索,找到了一个比较好的解决方案,就是利用下面这个函数:window.requestAnimationFrame()

requestAnimationFrame()引入

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

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

window.requestAnimationFrame查看相关文档),顾名思义就是请求动画帧,去告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

注意:若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()

当你准备更新动画时你应该调用此方法。这将使浏览器在下一次重绘之前调用你传入给该方法的动画函数(即你的回调函数)。回调函数执行次数通常是每秒60次,但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的<iframe> 里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。

回调函数会被传入DOMHighResTimeStamp参数,DOMHighResTimeStamp指示由RequestAnimationFrame()排队的回调开始触发的时间。指示当前被 requestAnimationFrame() 排序的回调函数被触发的时间。在同一个帧中的多个回调函数,它们每一个都会接受到一个相同的时间戳,即使在计算上一个回调函数的工作负载期间已经消耗了一些时间。该时间戳是一个十进制数,单位毫秒,最小精度为1ms(1000μs)。

requestAnimationFrame()对比

setTimeoutsetInterval相比——

  • setTimeoutsetInterval的问题是,它们都不精确。因为javascript是单线程的,它们的内在运行机制决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行。
  • requestAnimationFrame采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。

requestAnimationFrame()特点

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

requestAnimationFrame()语法

window.requestAnimationFrame(callback);

参数

**callback**下一次重绘之前更新动画帧所调用的函数(即上面所说的回调函数)。该回调函数会被传入DOMHighResTimeStamp参数,该参数与performance.now()的返回值相同,它表示requestAnimationFrame() 开始去执行回调函数的时刻。

返回值

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

rAF(requestAnimationFrame)

网上很多教程里都有这个“rAF()”函数,看它的英文名全称就知道其实没什么神秘的,如果你喜欢,也可以叫“XXOO()”。因为目前,主要浏览器Firefox 23 / IE 10 / Chrome / Safari)都支持这个方法。可以用下面的方法,检查浏览器是否支持这个API。如果不支持,则自行模拟部署该方法。

window.requestAnimFrame = (function(){
  return  window.requestAnimationFrame       || 
          window.webkitRequestAnimationFrame || 
          window.mozRequestAnimationFrame    || 
          window.oRequestAnimationFrame      || 
          window.msRequestAnimationFrame     || 
          function( callback ){
            window.setTimeout(callback, 1000 / 60);
          };
})();

获取浏览器刷新帧率FPS

一共两种方式:“纯requestAnimationFrame方式”和“rAF()获取方式”

两个调用方式都是直接使用如下代码:

loopToGetFps();

纯requestAnimationFrame方式

获取浏览器帧率代码如下:

方案一:固定时间

//此代码运行于chrome版本 75.0.3770.100(正式版本)snap (64 位)


//fps variales
var frame = 0;
var allFrameCount = 0;
var lastTime = Date.now();
var lastFameTime = Date.now();
var preFps = 0;

var loopToGetFps = function () {
    var now = Date.now();  //获取当前时间
    var fs = (now - lastFameTime);  //计算时间差
    var fps = Math.round(1000 / fs); //计算帧率

    lastFameTime = now;
    // not set 0, to get FPS by recording the difference in this value at the beginning and end of the animation 
    allFrameCount++;
    frame++;

    if (now > 1000 + lastTime) {
        var fps = Math.round((frame * 1000) / (now - lastTime));
        
        if (preFps != fps) {
            console.log("fps:", fps);
            preFps = fps;
        }
        frame = 0;
        lastTime = now;
    };
    window.requestAnimationFrame(loopToGetFps);
}

方案二:固定帧数

//此代码运行于chrome版本 75.0.3770.100(正式版本)snap (64 位)



//fps variales
var frame = 0;
var allFrameCount = 0;
var lastTime = Date.now();
var lastFameTime = Date.now();
var preFps = 0;

var now = Date.now();
var fps = 0;
var sendFps = 0;

var loopToGetFps = function () {
    frame++;
    if (frame >= 60) {
        now = Date.now();
        fps = Math.round((frame * 1000) / (now - lastTime));
        console.log("fps:", fps);
        
        if (preFps != fps) {
            sendFps = "fpsNum" + fps;
            if (alexaFpsFlag) {
                sendMessage(sendFps);
            }
            preFps = fps;
        }
        frame = 0;
        lastTime = now;
    };
    window.requestAnimationFrame(loopToGetFps);
}

rAF()获取浏览器刷新帧率FPS

之所以采用“rAF()”,是为了更好地适配兼容各种浏览器,获取浏览器帧率代码如下:

//fps variales
var frame = 0;
var allFrameCount = 0;
var lastTime = Date.now();
var lastFameTime = Date.now();
var preFps = 0;

var rAF = function () {
    return (
        window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        function (callback) {
            window.setTimeout(callback, 1000 / 60);
        }
    );
}();

var loopToGetFps = function () {
    var now = Date.now();
    var fs = (now - lastFameTime);
    var fps = Math.round(1000 / fs);

    lastFameTime = now;
    // not set 0, to get FPS by recording the difference in this value at the beginning and end of the animation 
    allFrameCount++;
    frame++;

    if (now > 1000 + lastTime) {
        var fps = Math.round((frame * 1000) / (now - lastTime));
        console.log("fps:", fps);
        
        if (preFps != fps) {
            var sendFps = "fpsNum" + fps;
            if (alexaFpsFlag) {
                sendMessage(sendFps);
            }
            preFps = fps;
        }
        frame = 0;
        lastTime = now;
    };
    rAF(loopToGetFps);
}

致谢

参考资料过多,不一一列举了,在此表示感谢。