如何計(jì)算Web動(dòng)畫幀率FPS
流暢動(dòng)畫的標(biāo)準(zhǔn)
首先,理清一些概念。FPS 表示的是每秒鐘畫面更新次數(shù)。我們平時(shí)所看到的連續(xù)畫面都是由一幅幅靜止畫面組成的,每幅畫面稱為一幀,F(xiàn)PS 是描述“幀”變化速度的物理量。
理論上說,F(xiàn)PS 越高,動(dòng)畫會(huì)越流暢,目前大多數(shù)設(shè)備的屏幕刷新率為 60 次/秒,所以通常來講 FPS 為 60 frame/s 時(shí)動(dòng)畫效果最好,也就是每幀的消耗時(shí)間為 16.67ms。
當(dāng)然,經(jīng)常玩 FPS 游戲的朋友肯定知道,吃雞/CSGO 等 FPS 游戲推薦使用 144HZ 刷新率的顯示器,144Hz 顯示器特指每秒的刷新率達(dá)到 144Hz 的顯示器。相較于普通顯示器每秒60的刷新速度,畫面顯示更加流暢。因此144Hz顯示器比較適用于視角時(shí)常保持高速運(yùn)動(dòng)的第一人稱射擊游戲。
不過,這個(gè)只是顯示器提供的高刷新率特性,對(duì)于我們 Web 動(dòng)畫而言,是否支持還要看瀏覽器,而大多數(shù)瀏覽器刷新率為 60 次/秒。
直觀感受,不同幀率的體驗(yàn):
- 幀率能夠達(dá)到 50 ~ 60 FPS 的動(dòng)畫將會(huì)相當(dāng)流暢,讓人倍感舒適;
- 幀率在 30 ~ 50 FPS 之間的動(dòng)畫,因各人敏感程度不同,舒適度因人而異;
- 幀率在 30 FPS 以下的動(dòng)畫,讓人感覺到明顯的卡頓和不適感;
- 幀率波動(dòng)很大的動(dòng)畫,亦會(huì)使人感覺到卡頓。
OK,那么我們?cè)撊绾螠?zhǔn)確的獲取我們頁面動(dòng)畫當(dāng)前的 FPS 值呢?
法一:借助 Chrome 開發(fā)者工具
Chrome 提供給開發(fā)者的功能十分強(qiáng)大,在開發(fā)者工具中,我們進(jìn)行如下選擇調(diào)出FPS meter選項(xiàng):
通過這個(gè)按鈕,可以開啟頁面實(shí)時(shí) Frame Rate (幀率) 觀測(cè)及頁面 GPU 使用率。
缺點(diǎn):
- 這個(gè)只能一次觀測(cè)一到幾個(gè)頁面,而且需要人工實(shí)時(shí)觀測(cè)
- 數(shù)據(jù)只能是主觀感受,并沒有一個(gè)十分精確的數(shù)據(jù)不斷上報(bào)或者被收集
因此,我們需要更加智能的方法。
法二:借助 Frame Timing API
在介紹下面這種方法前,繼續(xù)做一些基礎(chǔ)知識(shí)的科普。
Blink 內(nèi)核早期架構(gòu)
以 Chrome 瀏覽器內(nèi)核 Blink 渲染頁面為例。對(duì)早期的 Chrome 瀏覽器而言,每個(gè)頁面 Tab 對(duì)應(yīng)一個(gè)獨(dú)立的 renderer 進(jìn)程,Renderer 進(jìn)程中包含了主線程和合成線程。早期 Chrome 內(nèi)核架構(gòu):
其中,主線程主要負(fù)責(zé):
- Javascript 的計(jì)算與執(zhí)行
- CSS 樣式計(jì)算
- Layout 計(jì)算
- 將頁面元素繪制成位圖(paint),也就是光柵化(Raster)
- 將位圖給合成線程
合成線程則主要負(fù)責(zé):
- 將位圖(GraphicsLayer 層)以紋理(texture)的形式上傳給 GPU
- 計(jì)算頁面的可見部分和即將可見部分(滾動(dòng))
- CSS 動(dòng)畫處理
- 通知 GPU 繪制位圖到屏幕上
OK,云里霧里的,什么東西。其實(shí)知道了這兩個(gè)線程之后,下一個(gè)概念是厘清 CSS 動(dòng)畫與 JS 動(dòng)畫的細(xì)微區(qū)別(當(dāng)然它們都是 Web 動(dòng)畫)。
JS 動(dòng)畫與 CSS 動(dòng)畫的細(xì)微區(qū)別
對(duì)于 JS 動(dòng)畫而言,它們運(yùn)行時(shí)的幀率即是主線程和合成線程加起來消耗的時(shí)間。對(duì)于流暢動(dòng)畫而言,我們希望它們每一幀的耗時(shí)保持在 16.67ms 之內(nèi);
而對(duì)于 CSS 動(dòng)畫而言,由于其流程不受主線程的影響,所以希望能得到合成線程的消耗的時(shí)間,而合成線程的繪制頻率也反映了滾動(dòng)和 CSS 動(dòng)畫的流程性。
上面主要想得出的一個(gè)結(jié)論是。如果我們能夠知道主線程和合成線程每一幀消耗的時(shí)間,那么我們就能大致得出對(duì)應(yīng)的 Web 動(dòng)畫的幀率。那么上面說到的Frame Timing API是否可以幫助我們拿到這個(gè)時(shí)間點(diǎn)呢。
什么是 Frame Timing API ?
Frame Timing API 是 Web Performance Timing API 標(biāo)準(zhǔn)中的其中一位成員。
Web Performance Timing API是 W3C 推出的一套性能API 標(biāo)準(zhǔn),用于幫助開發(fā)者對(duì)網(wǎng)站各方面的性能進(jìn)行精確的分析與控制,提升 Web 網(wǎng)站性能。
它包含許多子類 API,完成不同的功能,大致如下(摘自使用性能API快速分析web前端性能,當(dāng)然你也可以看英文原版介紹:Web Performance Timing API):
怎么使用呢?以Navigation Timing, Performance Timeline, Resource Timing為例子,對(duì)于兼容它的瀏覽器,它以只讀屬性的形式對(duì)外暴露掛載在window.performance上。
在調(diào)試臺(tái) console 中打印window.performance,查看其中的 timing 屬性:
這對(duì)象內(nèi)一連串的變量表示什么呢,它表示我們頁面整個(gè)加載過程中每一個(gè)重要的時(shí)間點(diǎn),可以詳細(xì)看看這張圖:
通過這張圖以及上面的window.performance.timing,我們就可以輕松的統(tǒng)計(jì)出頁面每個(gè)重要節(jié)點(diǎn)的耗時(shí),這就是Web Performance Timing API的強(qiáng)大之處,感興趣的可以詳細(xì)去研究研究,使用在頁面統(tǒng)計(jì)上。
Frame Timing API 示意
好的,終于可以回歸正題,借助Web Performance Timing API中的Frame Timing API,可以輕松的拿到每一幀中,主線程以及合成線程的時(shí)間?;蛘吒尤菀?,直接拿到每一幀的耗時(shí)。
獲取 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 }
每個(gè)記錄都包括唯一的 Frame Number、Frame 開始時(shí)間以及 cpuTime 時(shí)間。通過計(jì)算每一條記錄的 startTime ,我們就可以算出每?jī)蓭g的間隔,從而得到動(dòng)畫的幀率是否能夠達(dá)到 60 FPS。
不過!看看Web Performance Timing API整體的兼容性:
Frame Timing API 雖好,但是,現(xiàn)在Frame Timing API的兼容性不算很友好,額,不友好到什么程度呢。還沒有任何瀏覽器支持,處于實(shí)驗(yàn)性階段,屬于面向未來編程。這你 TM 逗我呢?說了這么久完全不能用.....
法三:借助 requestAnimationFrame API
費(fèi)了這么多筆墨描述 Frame Timing API 但最后因?yàn)榧嫒菪詥栴}完全沒辦法使用。不過不代表這么長(zhǎng)篇幅的描述沒有用,從上面的介紹,我們得知,如果我們可以到得到每一幀中的固定一個(gè)時(shí)間點(diǎn),那么兩者相減,也能夠近似得到一幀所消耗的時(shí)間。
那么,我們?cè)倭肀脔鑿?。這次,我們借助兼容性不錯(cuò)的 requestAnimationFrame API。
// 語法 window.requestAnimationFrame(callback);
requestAnimationFrame大家應(yīng)該都不陌生,方法告訴瀏覽器您希望執(zhí)行動(dòng)畫并請(qǐng)求瀏覽器調(diào)用指定的函數(shù)在下一次重繪之前更新動(dòng)畫。
當(dāng)你準(zhǔn)備好更新屏幕畫面時(shí)你就應(yīng)用此方法。這會(huì)要求你的動(dòng)畫函數(shù)在瀏覽器下次重繪前執(zhí)行。回調(diào)的次數(shù)常是每秒 60 次,大多數(shù)瀏覽器通常匹配 W3C 所建議的刷新率。
使用 requestAnimationFrame 計(jì)算 FPS 原理
原理是,正常而言 requestAnimationFrame 這個(gè)方法在一秒內(nèi)會(huì)執(zhí)行 60 次,也就是不掉幀的情況下。假設(shè)動(dòng)畫在時(shí)間 A 開始執(zhí)行,在時(shí)間 B 結(jié)束,耗時(shí) x ms。而中間 requestAnimationFrame 一共執(zhí)行了 n 次,則此段動(dòng)畫的幀率大致為:n / (B - A)。
核心代碼如下,能近似計(jì)算每秒頁面幀率,以及我們額外記錄一個(gè) allFrameCount,用于記錄 rAF 的執(zhí)行次數(shù),用于計(jì)算每次動(dòng)畫的幀率 :
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,在動(dòng)畫的開頭及結(jié)尾記錄此值的差值算出 FPS allFrameCount++; frame++; if (now > 1000 + lastTime) { var fps = Math.round((frame * 1000) / (now - lastTime)); console.log(`${new Date()} 1S內(nèi) FPS:`, fps); frame = 0; lastTime = now; }; rAF(loop); } loop();
OK,尋找一個(gè)有動(dòng)畫不斷運(yùn)行的頁面進(jìn)行測(cè)試,可以看到代碼運(yùn)行如下:
這里,我使用了我之前制作的一個(gè)頁面進(jìn)行了測(cè)試,使用 Chrome 同時(shí)調(diào)出頁面的 FPS meter,對(duì)比兩邊的實(shí)時(shí) FPS 值,基本吻合。
測(cè)試頁面,Solar System。你可以將上面的代碼貼到這個(gè)頁面的 console 中,測(cè)試一下數(shù)據(jù):
對(duì)比右上角的 Frame Rate,幀率基本一致。在大部分情況下,這種方法可以很好的得出 Web 動(dòng)畫的幀率。
如果我們需要統(tǒng)計(jì)某個(gè)特定動(dòng)畫過程的幀率,只需要在動(dòng)畫開始和結(jié)尾兩處分別記錄allFrameCount這個(gè)數(shù)值大小,再除以中間消耗的時(shí)間,也可以得出特定動(dòng)畫過程的 FPS 值。
值得注意的是,這個(gè)方法計(jì)算的結(jié)果和真實(shí)的幀率肯定是存在誤差的,因?yàn)樗菍⒚績(jī)纱沃骶€程執(zhí)行 javascript 的時(shí)間間隔當(dāng)成一幀,而非上面說的主線程加合成線程所消耗的時(shí)間為一幀。但是對(duì)于現(xiàn)階段而言,算是一種可取的方法。
以上就是如何計(jì)算Web動(dòng)畫幀率FPS的詳細(xì)內(nèi)容,更多關(guān)于計(jì)算Web動(dòng)畫幀率FPS的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript語法 JSON序列化之stringify實(shí)例詳解
這篇文章主要為大家介紹了JavaScript語法 JSON序列化之stringify實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10JavaScript實(shí)現(xiàn)九宮格移動(dòng)拼圖游戲
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)九宮格移動(dòng)拼圖游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08js canvas實(shí)現(xiàn)寫字動(dòng)畫效果
這篇文章主要為大家詳細(xì)介紹了js canvas實(shí)現(xiàn)寫字動(dòng)畫效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11javascript運(yùn)算符——邏輯運(yùn)算符全面解析
下面小編就為大家?guī)硪黄猨avascript運(yùn)算符——邏輯運(yùn)算符詳解。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-06-06Echarts讀取動(dòng)態(tài)數(shù)據(jù)完整代碼
這篇文章主要給大家介紹了關(guān)于Echarts讀取動(dòng)態(tài)數(shù)據(jù)的相關(guān)資料,使用Echarts畫圖時(shí),數(shù)據(jù)一般不是靜態(tài)寫死的,而是通過后端接口動(dòng)態(tài)獲取的,需要的朋友可以參考下2023-10-10JavaScript數(shù)據(jù)推送Comet技術(shù)詳解
這篇文章主要為大家詳細(xì)介紹了JavaScript數(shù)據(jù)推送Comet技術(shù),感興趣的小伙伴們可以參考一下2016-04-04JS實(shí)現(xiàn)調(diào)用本地?cái)z像頭功能示例
這篇文章主要介紹了JS實(shí)現(xiàn)調(diào)用本地?cái)z像頭功能,結(jié)合實(shí)例形式分析了Javascript基于瀏覽器對(duì)本地硬件操作簡(jiǎn)單實(shí)現(xiàn)技巧,需要的朋友可以參考下2018-05-05