JS前端性能指標(biāo)定位FMP使用詳解
什么是FMP?
可能大家對(duì)「白屏?xí)r間」這個(gè)名詞并不陌生,他是「刀耕火種」年代,我們收集的頁(yè)面性能指標(biāo)之一,隨著前端工程的復(fù)雜化,白屏?xí)r間已經(jīng)沒(méi)有什么實(shí)質(zhì)性的意義了,取而代之的就是 FMP。
先來(lái)介紹幾個(gè)與之相關(guān)的名詞。
- FP(First Paint):首次繪制,標(biāo)記瀏覽器渲染任何在視覺(jué)上不同于導(dǎo)航前屏幕內(nèi)容的時(shí)間點(diǎn)
- FCP(First Contentful Paint):首次內(nèi)容繪制,標(biāo)記的是瀏覽器渲染第一針內(nèi)容 DOM 的時(shí)間點(diǎn),該內(nèi)容可能是文本、圖像、SVG 或者
<canvas>
等元素 - FMP(First Meaning Paint):首次有效繪制,標(biāo)記主角元素渲染完成的時(shí)間點(diǎn),主角元素可以是視頻網(wǎng)站的視頻控件,內(nèi)容網(wǎng)站的頁(yè)面框架也可以是資源網(wǎng)站的頭圖等。
相對(duì)于 FP 和 FCP,F(xiàn)MP 是我們前端最常關(guān)注的重要性能指標(biāo),Google 定義它為「是否有用?」的時(shí)間點(diǎn)。然而,「是否有用?」是很難以通用方式界定的,因此,至今依然沒(méi)有標(biāo)準(zhǔn)的 API 輸出。
社區(qū)中常有這么幾種方式進(jìn)行「相對(duì)準(zhǔn)確」的計(jì)算 FMP,所謂相對(duì)準(zhǔn)確,是相對(duì)于實(shí)際項(xiàng)目而言。
- 主動(dòng)上報(bào):開(kāi)發(fā)者在相應(yīng)頁(yè)面的「Meaning」位置上報(bào)時(shí)間
- 權(quán)重計(jì)算:根據(jù)頁(yè)面元素,計(jì)算權(quán)重最高的元素渲染時(shí)間
- 趨勢(shì)計(jì)算:在 render 期間,根據(jù) dom 的變化趨勢(shì)推算 FMP 值
本文將著重介紹第二種方式。
權(quán)重定位
所謂權(quán)重,即,將頁(yè)面的元素以約定的「權(quán)重比」遍歷出「權(quán)重值」最大的某一個(gè)或一組 DOM,然后以其「裝載時(shí)間點(diǎn)」或「加載結(jié)束點(diǎn)」作為 FMP 的映射。
權(quán)重計(jì)算
節(jié)點(diǎn)標(biāo)記
想要對(duì) DOM 節(jié)點(diǎn)進(jìn)行階段性標(biāo)記,就得有監(jiān)聽(tīng) DOM 變化的能力,慶幸的是,HTML5 賦予了我們這個(gè)能力。
MutationObserver
,Mutation Events功能的替代品,是DOM3 Events規(guī)范的一部分。他可以在指定的 DOM 發(fā)生變化時(shí)執(zhí)行回調(diào)。
MutationObserver 有三個(gè)方法
- disconnect() 阻止 MutationObserver 實(shí)例繼續(xù)接收的通知,直到再次調(diào)用其observe()方法,該觀察者對(duì)象包含的回調(diào)函數(shù)都不會(huì)再被調(diào)用。
- observe() 配置MutationObserver在DOM更改匹配給定選項(xiàng)時(shí),通過(guò)其回調(diào)函數(shù)開(kāi)始接收通知。
- takeRecords() 從MutationObserver的通知隊(duì)列中刪除所有待處理的通知,并將它們返回到MutationRecord對(duì)象的新Array中。
global.mo = new MutationObserver(() => { /* callback: DOM 節(jié)點(diǎn)設(shè)置階段性標(biāo)記 */ }); /** * mutationObserver.observe(target[, options]) * target - 需要觀察變化的 DOM Node。 * options - MutationObserverInit 對(duì)象,配置需要觀察的變化項(xiàng)。 * 更多 options 的介紹請(qǐng)參考 https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserverInit#%E5%B1%9E%E6%80%A7 **/ global.mo.observe(document, { childList: true, // 監(jiān)聽(tīng)子節(jié)點(diǎn)變化(如果subtree為true,則包含子孫節(jié)點(diǎn)) subtree: true // 整個(gè)子樹(shù)的所有節(jié)點(diǎn) });
下圖粗濾的解析了正常單頁(yè)面的渲染過(guò)程
- 預(yù)備階段:導(dǎo)航階段,處在連接相應(yīng)的過(guò)程
- 階段一:首字節(jié)渲染階段,也是FCP,DOM 樹(shù)的第一次有效變化
- 階段二:基本框架渲染完成
- 階段三:獲取到數(shù)據(jù),渲染到視圖上
- 階段四:圖片加載完成,加載過(guò)程不被標(biāo)記
實(shí)際上在第一、第三階段之間還存在著大量的 DOM 變化,Mutation Observer 事件的觸發(fā)并不是同步的,而是異步觸發(fā)的,也就是說(shuō),等到當(dāng)前「階段」所有 DOM 操作都結(jié)束才觸發(fā)。
Mutation Observer 有以下特點(diǎn)
- 它等待所有腳本任務(wù)完成后,才會(huì)運(yùn)行(即異步觸發(fā)方式)。
- 它把 DOM 變動(dòng)記錄封裝成一個(gè)數(shù)組進(jìn)行處理,而不是一條條個(gè)別處理 DOM 變動(dòng)。
- 它既可以觀察 DOM 的所有類(lèi)型變動(dòng),也可以指定只觀察某一類(lèi)變動(dòng)。
在 load
事件觸發(fā)后,各個(gè)階段的 tag 已經(jīng)被打到標(biāo)簽上了
此處以『_ti
』昨晚標(biāo)記 key。
在打標(biāo)記的同時(shí),需要記錄下當(dāng)前的時(shí)間節(jié)點(diǎn),備用
// 偽代碼 function callback() { global.timeStack[++_ti] = performance.now(); // 記時(shí)間 doTag(_ti); // 打標(biāo)記 }
標(biāo)記打完后就等 load 的那一刻進(jìn)行計(jì)算反推了。
計(jì)算權(quán)重值
一般來(lái)說(shuō)
- 視圖占比越大的元素越有可能是主角元素
- 視頻比圖片更可能是主角元素
svg
和canvas
也很重要- 其他元素都可以按普通 dom 計(jì)算了
- 背景圖片視情況而定,可記可不記
第一步:簡(jiǎn)單粗暴,按大小計(jì)算
// 偽代碼 function weightCompute(node){ let { width, height, left, top } = node.getBoundingClientRect(); // 排除視圖外的元素 if(isOutside(width, height, left, top)){ return 0; } let wts = TAG_WEIGHT_MAP[node.tagName]; // 約定好的權(quán)重比 let weight = width * height * wts; // 直接乘,或者更細(xì)粒度的計(jì)算 wts(width, height, wts) return { weight, wts, tagName: node.tagName, ti: node.getAttribute("_ti"), node }; }
第二步:根據(jù)權(quán)重值推導(dǎo)主角元素
在我們的約定權(quán)重算法下,權(quán)重最大的元素即為我們推到的主角元素。
// 偽代碼 function getCoreNode(node){ let list = nodeTraversal(node); // 遞歸計(jì)算每個(gè)標(biāo)記節(jié)點(diǎn)的權(quán)重值 return getNodeWithMaxWeight(list); // weight 最大的元素 }
第三步:根據(jù)元素類(lèi)型取時(shí)間
不同的元素獲取時(shí)間的方式并不相同
- 普通元素:按標(biāo)記點(diǎn)時(shí)間計(jì)算
- 圖片和視頻:按資源相應(yīng)結(jié)束時(shí)間計(jì)算
- 帶背景元素:可以以背景資源相應(yīng)結(jié)束時(shí)間計(jì)算,也可以按普通元素計(jì)算
// 偽代碼 function getFMP(){ let coreObj = getCoreNode(document.body), fmp = -1; let { tagName, ti, node } = coreObj; switch(tagName){ case 'IMG': case 'VIDEO': let source = node.src; let { responseEnd } = performance.getEntries().find(item => item.name === source); fmp = responseEnd || -1; break; default: if(node.style.backgroundImage){ // 普通元素的背景處理 }else{ fmp = global.timeStack[+ti]; } } return fmp; }
回歸驗(yàn)證
以我們的 demo 頁(yè)為例,類(lèi)似的電商網(wǎng)站,我們希望拿到「階段二」或「階段三」的時(shí)間點(diǎn)作為我們的 FMP 值。
因?yàn)槲覀儾⒉幌M钢鹘窃亍沟谋尘盎蛘摺笀D片主角元素」的相應(yīng)時(shí)間算在 FMP 的值內(nèi),所以,我們將「圖片」「視頻」等資源元素降級(jí)成普通元素計(jì)算。
在 Chrome [ Disable cache / Fast 3G ] 條件下我們進(jìn)行模擬驗(yàn)證。
計(jì)算得到的 FMP 值為 4730.7ms,Chrome Performance 監(jiān)控的值在 4950ms 左右,誤差在 200ms 左右。
如果將限速放開(kāi),F(xiàn)MP 的取值將更接近我們希望的「First Meaning Paint」。
以上就是JS前端性能指標(biāo)定位FMP使用詳解的詳細(xì)內(nèi)容,更多關(guān)于JS前端性能指標(biāo)定位FMP的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
微信小程序組件 contact-button(客服會(huì)話按鈕)詳解及實(shí)例代碼
這篇文章主要介紹了微信小程序組件 contact-button(客服會(huì)話按鈕)詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-01-01umi插件開(kāi)發(fā)仿dumi項(xiàng)目自動(dòng)生成導(dǎo)航欄實(shí)現(xiàn)詳解
這篇文章主要為大家介紹了umi插件開(kāi)發(fā)仿dumi項(xiàng)目自動(dòng)生成導(dǎo)航欄實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01sessionStorage多Tab標(biāo)簽頁(yè)數(shù)據(jù)共享問(wèn)題分析
這篇文章主要為大家介紹了sessionStorage多Tab標(biāo)簽頁(yè)數(shù)據(jù)共享問(wèn)題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07jQuery單頁(yè)面文字搜索插件jquery.fullsearch.js的使用方法
jquery.fullsearch.js是一款基于Bootstrap文字搜索插件,可以幫助您快速搜索到當(dāng)前頁(yè)面所包含的指定文字,并定位到所在位置2020-02-02可拖動(dòng)窗口,附帶鼠標(biāo)控制漸變透明,開(kāi)啟關(guān)閉功能
可拖動(dòng)窗口,附帶鼠標(biāo)控制漸變透明,開(kāi)啟關(guān)閉功能...2006-06-06詳解如何發(fā)布TypeScript編寫(xiě)的npm包
這篇文章主要介紹了如何發(fā)布TypeScript編寫(xiě)的npm包實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12JavaScript實(shí)現(xiàn)余額數(shù)字滾動(dòng)效果
這篇文章主要介紹了JavaScript實(shí)現(xiàn)余額數(shù)字滾動(dòng)效果,將傳入的帶滾動(dòng)的n位數(shù)字拆分成每一個(gè)要滾動(dòng)的數(shù),然后動(dòng)態(tài)的創(chuàng)建裝著滾動(dòng)到每一位相應(yīng)數(shù)字的容器,然后放入傳入的目標(biāo)容器中,更多詳細(xì)內(nèi)容,一起進(jìn)入下面文章學(xué)習(xí)吧2021-12-12