天天看點

Web 動畫幀率(FPS)計算

首先,理清一些概念。fps 表示的是每秒鐘畫面更新次數。我們平時所看到的連續畫面都是由一幅幅靜止畫面組成的,每幅畫面稱為一幀,fps 是描述“幀”變化速度的實體量。

理論上說,fps 越高,動畫會越流暢,目前大多數裝置的螢幕重新整理率為 60 次/秒,是以通常來講 fps 為 60 frame/s 時動畫效果最好,也就是每幀的消耗時間為 16.67ms。

當然,經常玩 fps 遊戲的朋友肯定知道,吃雞/csgo 等 fps 遊戲推薦使用 144hz 重新整理率的顯示器,144hz 顯示器特指每秒的重新整理率達到 144hz 的顯示器。相較于普通顯示器每秒60的重新整理速度,畫面顯示更加流暢。是以144hz顯示器比較适用于視角時常保持高速運動的第一人稱射擊遊戲。不過,這個隻是顯示器提供的高重新整理率特性,對于我們 web 動畫而言,是否支援還要看浏覽器,而大多數浏覽器重新整理率為 60 次/秒。

直覺感受,不同幀率的體驗:

幀率能夠達到 50 ~ 60 fps 的動畫将會相當流暢,讓人倍感舒适;

幀率在 30 ~ 50 fps 之間的動畫,因各人敏感程度不同,舒适度因人而異;

幀率在 30 fps 以下的動畫,讓人感覺到明顯的卡頓和不适感;

幀率波動很大的動畫,亦會使人感覺到卡頓。

ok,那麼我們該如何準确的擷取我們頁面動畫目前的 fps 值呢?

chrome 提供給開發者的功能十分強大,在開發者工具中,我們進行如下選擇調出 fps meter 選項:

Web 動畫幀率(FPS)計算

通過這個按鈕,可以開啟頁面實時 frame rate (幀率) 觀測及頁面 gpu 使用率。

但是這個方法缺點太多了,

這個隻能一次觀測一到幾個頁面,而且需要人工實時觀測

資料隻能是主觀感受,并沒有一個十分精确的資料不斷上報或者被收集

是以,我們需要更加智能的方法。

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

以 chrome 浏覽器核心 blink 渲染頁面為例。對早期的 chrome 浏覽器而言,每個頁面 tab 對應一個獨立的 renderer 程序,renderer 程序中包含了主線程和合成線程。早期 chrome 核心架構:

Web 動畫幀率(FPS)計算

其中,主線程主要負責:

javascript 的計算與執行

css 樣式計算

layout 計算

将頁面元素繪制成位圖(paint),也就是光栅化(raster)

将位圖給合成線程

合成線程則主要負責:

将位圖(graphicslayer 層)以紋理(texture)的形式上傳給 gpu

計算頁面的可見部分和即将可見部分(滾動)

css 動畫處理

通知 gpu 繪制位圖到螢幕上

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 ):

Web 動畫幀率(FPS)計算

怎麼使用呢?以 navigation timing, performance timeline, resource timing 為例子,對于相容它的浏覽器,它以隻讀屬性的形式對外暴露挂載在 window.performance 上。

在調試台 console 中列印 window.performance ,檢視其中的 timing 屬性:

Web 動畫幀率(FPS)計算

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

Web 動畫幀率(FPS)計算

通過這張圖以及上面的 window.performance.timing,我們就可以輕松的統計出頁面每個重要節點的耗時,這就是 web performance timing api 的強大之處,感興趣的可以詳細去研究研究,使用在頁面統計上。

好的,終于可以回歸正題,借助 web performance timing api 中的 frame timing api,可以輕松的拿到每一幀中,主線程以及合成線程的時間。或者更加容易,直接拿到每一幀的耗時。

擷取 render 主線程和合成線程的記錄,每條記錄包含的資訊基本如下,代碼示意,(參考至developer feedback needed: frame timing api):

或者是:

每條記錄包含的資訊基本如下:

每個記錄都包括唯一的 frame number、frame 開始時間以及 cputime 時間。通過計算每一條記錄的 starttime ,我們就可以算出每兩幀間的間隔,進而得到動畫的幀率是否能夠達到 60 fps。

不過!看看 web performance timing api 整體的相容性:

Web 動畫幀率(FPS)計算

frame timing api 雖好,但是,現在 frame timing api 的相容性不算很友好,額,不友好到什麼程度呢。還沒有任何浏覽器支援,處于實驗性階段,屬于面向未來程式設計。這你 tm 逗我呢?說了這麼久完全不能用.....

費了這麼多筆墨描述 frame timing api 但最後因為相容性問題完全沒辦法使用。不過不代表這麼長篇幅的描述沒有用,從上面的介紹,我們得知,如果我們可以到得到每一幀中的固定一個時間點,那麼兩者相減,也能夠近似得到一幀所消耗的時間。

那麼,我們再另辟蹊徑。這次,我們借助相容性不錯的 requestanimationframe api。

requestanimationframe 大家應該都不陌生,方法告訴浏覽器您希望執行動畫并請求浏覽器調用指定的函數在下一次重繪之前更新動畫。

當你準備好更新螢幕畫面時你就應用此方法。這會要求你的動畫函數在浏覽器下次重繪前執行。回調的次數常是每秒 60 次,大多數浏覽器通常比對 w3c 所建議的重新整理率。

原理是,正常而言 requestanimationframe 這個方法在一秒内會執行 60 次,也就是不掉幀的情況下。假設動畫在時間 a 開始執行,在時間 b 結束,耗時 x ms。而中間 requestanimationframe 一共執行了 n 次,則此段動畫的幀率大緻為:n / (b - a)。

核心代碼如下,能近似計算每秒頁面幀率,以及我們額外記錄一個 allframecount,用于記錄 raf 的執行次數,用于計算每次動畫的幀率 :

ok,尋找一個有動畫不斷運作的頁面進行測試,可以看到代碼運作如下:

Web 動畫幀率(FPS)計算

這裡,我使用了我之前制作的一個頁面進行了測試,使用 chrome 同時調出頁面的 fps meter,對比兩邊的實時 fps 值,基本吻合。

測試頁面,solar system。

對比右上角的 frame rate,幀率基本一緻。在大部分情況下,這種方法可以很好的得出 web 動畫的幀率。

如果我們需要統計某個特定動畫過程的幀率,隻需要在動畫開始和結尾兩處分别記錄 allframecount 這個數值大小,再除以中間消耗的時間,也可以得出特定動畫過程的 fps 值。

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