【前端性能】Web 動畫幀率(FPS)計算

咱們知道,動畫實際上是由一幀一幀的圖像構成的。有 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

  • 幀率可以達到 50 ~ 60 FPS 的動畫將會至關流暢,讓人倍感溫馨;
  • 幀率在 30 ~ 50 FPS 之間的動畫,因各人敏感程度不一樣,溫馨度因人而異;
  • 幀率在 30 FPS 如下的動畫,讓人感受到明顯的卡頓和不適感;
  • 幀率波動很大的動畫,亦會令人感受到卡頓。

OK,那麼咱們該如何準確的獲取咱們頁面動畫當前的 FPS 值呢?git

 

法一:藉助 Chrome 開發者工具

Chrome 提供給開發者的功能十分強大,在開發者工具中,咱們進行以下選擇調出 FPS meter 選項:github

image

經過這個按鈕,能夠開啓頁面實時 Frame Rate (幀率) 觀測及頁面 GPU 使用率。web

缺點

可是這個方法缺點太多了,編程

  • 這個只能一次觀測一到幾個頁面,並且須要人工實時觀測
  • 數據只能是主觀感覺,並無一個十分精確的數據不斷上報或者被收集

所以,咱們須要更加智能的方法。

 

法二:藉助 Frame Timing API

在介紹下面這種方法前,繼續作一些基礎知識的科普。

Blink 內核早期架構

以 Chrome 瀏覽器內核 Blink 渲染頁面爲例。對早期的 Chrome 瀏覽器而言,每一個頁面 Tab 對應一個獨立的 renderer 進程,Renderer 進程中包含了主線程和合成線程。早期 Chrome 內核架構:

renderprocess

其中,主線程主要負責:

  • Javascript 的計算與執行
  • CSS 樣式計算
  • Layout 計算
  • 將頁面元素繪製成位圖(paint),也就是光柵化(Raster)
  • 將位圖給合成線程

合成線程則主要負責:

  • 將位圖(GraphicsLayer 層)以紋理(texture)的形式上傳給 GPU
  • 計算頁面的可見部分和即將可見部分(滾動)
  • CSS 動畫處理
  • 通知 GPU 繪製位圖到屏幕上

OK,雲裏霧裏的,什麼東西。其實知道了這兩個線程以後,下一個概念是釐清 CSS 動畫與 JS 動畫的細微區別(固然它們都是 Web 動畫)。

 

JS 動畫與 CSS 動畫的細微區別

  • 對於 JS 動畫而言,它們運行時的幀率便是主線程和合成線程加起來消耗的時間。對於流暢動畫而言,咱們但願它們每一幀的耗時保持在 16.67ms 以內;

  • 而對於 CSS 動畫而言,因爲其流程不受主線程的影響,因此但願能獲得合成線程的消耗的時間,而合成線程的繪製頻率也反映了滾動和 CSS 動畫的流程性。

上面主要想得出的一個結論是。若是咱們可以知道主線程和合成線程每一幀消耗的時間,那麼咱們就能大體得出對應的 Web 動畫的幀率。那麼上面說到的 Frame Timing API 是否能夠幫助咱們拿到這個時間點呢。

 

什麼是 Frame Timing API ?

Frame Timing API 是 Web Performance Timing API 標準中的其中一位成員。

Web Performance Timing API 是 W3C 推出的一套性能 API 標準,用於幫助開發者對網站各方面的性能進行精確的分析與控制,提高 Web 網站性能。

它包含許多子類 API,完成不一樣的功能,大體以下(摘自使用性能API快速分析web前端性能,固然你也能夠看英文原版介紹:Web Performance Timing API ):

image

怎麼使用呢?以 Navigation Timing, Performance Timeline, Resource Timing 爲例子,對於兼容它的瀏覽器,它以只讀屬性的形式對外暴露掛載在 window.performance 上。

在調試臺 console 中打印 window.performance ,查看其中的 timing 屬性:

image

這對象內一連串的變量表示什麼呢,它表示咱們頁面整個加載過程當中每個重要的時間點,能夠詳細看看這張圖:

image

經過這張圖以及上面的 window.performance.timing,咱們就能夠輕鬆的統計出頁面每一個重要節點的耗時,這就是 Web Performance Timing API 的強大之處,感興趣的能夠詳細去研究研究,使用在頁面統計上。

 

Frame 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 總體的兼容性:

image

Frame Timing API 雖好,可是,如今 Frame Timing API 的兼容性不算很友好,額,不友好到什麼程度呢。尚未任何瀏覽器支持,處於實驗性階段,屬於面向將來編程。這你 TM 逗我呢?說了這麼久徹底不能用.....xx

 

法三:藉助 requestAnimationFrame API

費了這麼多筆墨描述 Frame Timing API 但最後由於兼容性問題徹底沒辦法使用。不過不表明這麼長篇幅的描述沒有用,從上面的介紹,咱們得知,若是咱們能夠到獲得每一幀中的固定一個時間點,那麼二者相減,也可以近似獲得一幀所消耗的時間。

那麼,咱們再另闢蹊徑。此次,咱們藉助兼容性不錯的 requestAnimationFrame API。

// 語法
window.requestAnimationFrame(callback);

requestAnimationFrame 你們應該都不陌生,方法告訴瀏覽器您但願執行動畫並請求瀏覽器調用指定的函數在下一次重繪以前更新動畫。

當你準備好更新屏幕畫面時你就應用此方法。這會要求你的動畫函數在瀏覽器下次重繪前執行。回調的次數常是每秒 60 次,大多數瀏覽器一般匹配 W3C 所建議的刷新率。

 

使用 requestAnimationFrame 計算 FPS 原理

原理是,正常而言 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,尋找一個有動畫不斷運行的頁面進行測試,能夠看到代碼運行以下:

image

這裏,我使用了我以前製做的一個頁面進行了測試,使用 Chrome 同時調出頁面的 FPS meter,對比兩邊的實時 FPS 值,基本吻合。

測試頁面,Solar System。你能夠將上面的代碼貼到這個頁面的 console 中,測試一下數據:

framefps

對比右上角的 Frame Rate,幀率基本一致。在大部分狀況下,這種方法能夠很好的得出 Web 動畫的幀率。

若是咱們須要統計某個特定動畫過程的幀率,只須要在動畫開始和結尾兩處分別記錄 allFrameCount 這個數值大小,再除以中間消耗的時間,也能夠得出特定動畫過程的 FPS 值。

值得注意的是,這個方法計算的結果和真實的幀率確定是存在偏差的,由於它是將每兩次主線程執行 javascript 的時間間隔當成一幀,而非上面說的主線程加合成線程所消耗的時間爲一幀。可是對於現階段而言,算是一種可取的方法。

 

參考文章

 

好了,本文到此結束,但願對你有幫助 :)

若是還有什麼疑問或者建議,能夠多多交流,原創文章,文筆有限,才疏學淺,文中如有不正之處,萬望告知。

相關文章
相關標籤/搜索