咱们知道,动画实际上是由一帧一帧的图像构成的。有 Web 动画那么就会存在该动画在播放运行时的帧率。而帧率在不一样设备不一样状况下又是不同的。javascript
有的时候,一些复杂或者重要动画,咱们须要实时监控它们的帧率,或者说是须要知道它们在不一样设备的运行情况,从而更好的优化它们,本文就是介绍 Web 动画帧率(FPS)计算方法。css
首先,理清一些概念。FPS 表示的是每秒钟画面更新次数。咱们平时所看到的连续画面都是由一幅幅静止画面组成的,每幅画面称为一帧,FPS 是描述“帧”变化速度的物理量。html
理论上说,FPS 越高,动画会越流畅,目前大多数设备的屏幕刷新率为 60 次/秒,因此一般来说 FPS 为 60 frame/s 时动画效果最好,也就是每帧的消耗时间为 16.67ms。前端
固然,常常玩 FPS 游戏的朋友确定知道,吃鸡/CSGO 等 FPS 游戏推荐使用 144HZ 刷新率的显示器,144Hz 显示器特指每秒的刷新率达到 144Hz 的显示器。相较于普通显示器每秒60的刷新速度,画面显示更加流畅。所以144Hz显示器比较适用于视角时常保持高速运动的第一人称射击游戏。
不过,这个只是显示器提供的高刷新率特性,对于咱们 Web 动画而言,是否支持还要看浏览器,而大多数浏览器刷新率为 60 次/秒。java
直观感觉,不一样帧率的体验:css3
OK,那么咱们该如何准确的获取咱们页面动画当前的 FPS 值呢?git
Chrome 提供给开发者的功能十分强大,在开发者工具中,咱们进行以下选择调出 FPS meter 选项:github
经过这个按钮,能够开启页面实时 Frame Rate (帧率) 观测及页面 GPU 使用率。web
可是这个方法缺点太多了,编程
所以,咱们须要更加智能的方法。
在介绍下面这种方法前,继续作一些基础知识的科普。
以 Chrome 浏览器内核 Blink 渲染页面为例。对早期的 Chrome 浏览器而言,每一个页面 Tab 对应一个独立的 renderer 进程,Renderer 进程中包含了主线程和合成线程。早期 Chrome 内核架构:
其中,主线程主要负责:
合成线程则主要负责:
OK,云里雾里的,什么东西。其实知道了这两个线程以后,下一个概念是厘清 CSS 动画与 JS 动画的细微区别(固然它们都是 Web 动画)。
对于 JS 动画而言,它们运行时的帧率便是主线程和合成线程加起来消耗的时间。对于流畅动画而言,咱们但愿它们每一帧的耗时保持在 16.67ms 以内;
而对于 CSS 动画而言,因为其流程不受主线程的影响,因此但愿能获得合成线程的消耗的时间,而合成线程的绘制频率也反映了滚动和 CSS 动画的流程性。
上面主要想得出的一个结论是。若是咱们可以知道主线程和合成线程每一帧消耗的时间,那么咱们就能大体得出对应的 Web 动画的帧率。那么上面说到的 Frame Timing API 是否能够帮助咱们拿到这个时间点呢。
Frame Timing API 是 Web Performance Timing API 标准中的其中一位成员。
Web Performance Timing API 是 W3C 推出的一套性能 API 标准,用于帮助开发者对网站各方面的性能进行精确的分析与控制,提高 Web 网站性能。
它包含许多子类 API,完成不一样的功能,大体以下(摘自使用性能API快速分析web前端性能,固然你也能够看英文原版介绍:Web Performance Timing API ):
怎么使用呢?以 Navigation Timing, Performance Timeline, Resource Timing
为例子,对于兼容它的浏览器,它以只读属性的形式对外暴露挂载在 window.performance
上。
在调试台 console 中打印 window.performance
,查看其中的 timing 属性:
这对象内一连串的变量表示什么呢,它表示咱们页面整个加载过程当中每个重要的时间点,能够详细看看这张图:
经过这张图以及上面的 window.performance.timing
,咱们就能够轻松的统计出页面每一个重要节点的耗时,这就是 Web Performance Timing API 的强大之处,感兴趣的能够详细去研究研究,使用在页面统计上。
好的,终于能够回归正题,借助 Web Performance Timing API 中的 Frame Timing API,能够轻松的拿到每一帧中,主线程以及合成线程的时间。或者更加容易,直接拿到每一帧的耗时。
获取 Render 主线程和合成线程的记录,每条记录包含的信息基本以下,代码示意,(参考至Developer feedback needed: Frame Timing API):
var rendererEvents = window.performance.getEntriesByType("renderer"); var compositeThreadEvents = window.performance.getEntriesByType("composite");
或者是:
var observer = new PerformanceObserver(function(list) { var perfEntries = list.getEntries(); for (var i = 0; i < perfEntries.length; i++) { console.log("frame: ", perfEntries[i]); } }); // subscribe to Frame Timing observer.observe({entryTypes: ['frame']});
每条记录包含的信息基本以下:
{ sourceFrameNumber: 120, startTime: 1342.549374253 cpuTime: 6.454313323 }
每一个记录都包括惟一的 Frame Number、Frame 开始时间以及 cpuTime 时间。经过计算每一条记录的 startTime ,咱们就能够算出每两帧间的间隔,从而获得动画的帧率是否可以达到 60 FPS。
不过!看看 Web Performance Timing API 总体的兼容性:
Frame Timing API 虽好,可是,如今 Frame Timing API 的兼容性不算很友好,额,不友好到什么程度呢。尚未任何浏览器支持,处于实验性阶段,属于面向将来编程。这你 TM 逗我呢?说了这么久彻底不能用.....
费了这么多笔墨描述 Frame Timing API 但最后由于兼容性问题彻底没办法使用。不过不表明这么长篇幅的描述没有用,从上面的介绍,咱们得知,若是咱们能够到获得每一帧中的固定一个时间点,那么二者相减,也可以近似获得一帧所消耗的时间。
那么,咱们再另辟蹊径。此次,咱们借助兼容性不错的 requestAnimationFrame API。
// 语法 window.requestAnimationFrame(callback);
requestAnimationFrame
你们应该都不陌生,方法告诉浏览器您但愿执行动画并请求浏览器调用指定的函数在下一次重绘以前更新动画。
当你准备好更新屏幕画面时你就应用此方法。这会要求你的动画函数在浏览器下次重绘前执行。回调的次数常是每秒 60 次,大多数浏览器一般匹配 W3C 所建议的刷新率。
原理是,正常而言 requestAnimationFrame 这个方法在一秒内会执行 60 次,也就是不掉帧的状况下。假设动画在时间 A 开始执行,在时间 B 结束,耗时 x ms。而中间 requestAnimationFrame 一共执行了 n 次,则此段动画的帧率大体为:n / (B - A)。
核心代码以下,能近似计算每秒页面帧率,以及咱们额外记录一个 allFrameCount,用于记录 rAF 的执行次数,用于计算每次动画的帧率 :
var rAF = function () { return ( window.requestAnimationFrame || window.webkitRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); } ); }(); var frame = 0; var allFrameCount = 0; var lastTime = Date.now(); var lastFameTime = Date.now(); var loop = function () { var now = Date.now(); var fs = (now - lastFameTime); var fps = Math.round(1000 / fs); lastFameTime = now; // 不置 0,在动画的开头及结尾记录此值的差值算出 FPS allFrameCount++; frame++; if (now > 1000 + lastTime) { var fps = Math.round((frame * 1000) / (now - lastTime)); console.log(`${new Date()} 1S内 FPS:`, fps); frame = 0; lastTime = now; }; rAF(loop); } loop();
OK,寻找一个有动画不断运行的页面进行测试,能够看到代码运行以下:
这里,我使用了我以前制做的一个页面进行了测试,使用 Chrome 同时调出页面的 FPS meter,对比两边的实时 FPS 值,基本吻合。
测试页面,Solar System。你能够将上面的代码贴到这个页面的 console 中,测试一下数据:
对比右上角的 Frame Rate,帧率基本一致。在大部分状况下,这种方法能够很好的得出 Web 动画的帧率。
若是咱们须要统计某个特定动画过程的帧率,只须要在动画开始和结尾两处分别记录 allFrameCount
这个数值大小,再除以中间消耗的时间,也能够得出特定动画过程的 FPS 值。
值得注意的是,这个方法计算的结果和真实的帧率确定是存在偏差的,由于它是将每两次主线程执行 javascript 的时间间隔当成一帧,而非上面说的主线程加合成线程所消耗的时间为一帧。可是对于现阶段而言,算是一种可取的方法。
好了,本文到此结束,但愿对你有帮助 :)
若是还有什么疑问或者建议,能够多多交流,原创文章,文笔有限,才疏学浅,文中如有不正之处,万望告知。