可能你們對「白屏時間」這個名詞並不陌生,他是「刀耕火種」年代,咱們收集的頁面性能指標之一,隨着前端工程的複雜化,白屏時間已經沒有什麼實質性的意義了,取而代之的就是 FMP。前端
先來介紹幾個與之相關的名詞。node
<canvas>
等元素相對於 FP 和 FCP,FMP 是咱們前端最常關注的重要性能指標,Google 定義它爲「是否有用?」的時間點。然而,「是否有用?」是很難以通用方式界定的,所以,至今依然沒有標準的 API 輸出。git
社區中常有這麼幾種方式進行「相對準確」的計算 FMP,所謂相對準確,是相對於實際項目而言。github
本文將着重介紹第二種方式。算法
所謂權重,即,將頁面的元素以約定的「權重比」遍歷出「權重值」最大的某一個或一組 DOM,而後以其「裝載時間點」或「加載結束點」做爲 FMP 的映射。canvas
想要對 DOM 節點進行階段性標記,就得有監聽 DOM 變化的能力,慶幸的是,HTML5 賦予了咱們這個能力。數組
MutationObserver
,Mutation Events功能的替代品,是DOM3 Events規範的一部分。他能夠在指定的 DOM 發生變化時執行回調。瀏覽器
MutationObserver 有三個方法框架
disconnect()
dom
阻止 MutationObserver 實例繼續接收的通知,直到再次調用其observe()方法,該觀察者對象包含的回調函數都不會再被調用。
observe()
配置MutationObserver在DOM更改匹配給定選項時,經過其回調函數開始接收通知。
takeRecords()
從MutationObserver的通知隊列中刪除全部待處理的通知,並將它們返回到MutationRecord對象的新Array中。
global.mo = new MutationObserver(() => {
/* callback: DOM 節點設置階段性標記 */
});
/** * mutationObserver.observe(target[, options]) * target - 須要觀察變化的 DOM Node。 * options - MutationObserverInit 對象,配置須要觀察的變化項。 * 更多 options 的介紹請參考 https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserverInit#%E5%B1%9E%E6%80%A7 **/
global.mo.observe(document, {
childList: true, // 監聽子節點變化(若是subtree爲true,則包含子孫節點)
subtree: true // 整個子樹的全部節點
});
複製代碼
下圖粗濾的解析了正常單頁面的渲染過程
實際上在第1、第三階段之間還存在着大量的 DOM 變化,Mutation Observer 事件的觸發並非同步的,而是異步觸發的,也就是說,等到當前「階段」全部 DOM 操做都結束才觸發。
Mutation Observer 有如下特色
- 它等待全部腳本任務完成後,纔會運行(即異步觸發方式)。
- 它把 DOM 變更記錄封裝成一個數組進行處理,而不是一條條個別處理 DOM 變更。
- 它既能夠觀察 DOM 的全部類型變更,也能夠指定只觀察某一類變更。
在 load
事件觸發後,各個階段的 tag 已經被打到標籤上了
此處以『_ti
』昨晚標記 key。
在打標記的同時,須要記錄下當前的時間節點,備用
// 僞代碼
function callback() {
global.timeStack[++_ti] = performance.now(); // 記時間
doTag(_ti); // 打標記
}
複製代碼
標記打完後就等 load 的那一刻進行計算反推了。
通常來講
svg
和 canvas
也很重要// 僞代碼
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]; // 約定好的權重比
let weight = width * height * wts; // 直接乘,或者更細粒度的計算 wts(width, height, wts)
return {
weight,
wts,
tagName: node.tagName,
ti: node.getAttribute("_ti"),
node
};
}
複製代碼
在咱們的約定權重算法下,權重最大的元素即爲咱們推到的主角元素。
// 僞代碼
function getCoreNode(node){
let list = nodeTraversal(node); // 遞歸計算每一個標記節點的權重值
return getNodeWithMaxWeight(list); // weight 最大的元素
}
複製代碼
不一樣的元素獲取時間的方式並不相同
// 僞代碼
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;
}
複製代碼
以咱們的 demo 頁爲例,相似的電商網站,咱們但願拿到「階段二」或「階段三」的時間點做爲咱們的 FMP 值。
由於咱們並不但願「主角元素」的背景或者「圖片主角元素」的相應時間算在 FMP 的值內,因此,咱們將「圖片」「視頻」等資源元素降級成普通元素計算。
在 Chrome [ Disable cache / Fast 3G ] 條件下咱們進行模擬驗證。
計算獲得的 FMP 值爲 4730.7ms,Chrome Performance 監控的值在 4950ms 左右,偏差在 200ms 左右。
若是將限速放開,FMP 的取值將更接近咱們但願的「First Meaning Paint」。
轉載請標明出處
做者: 木羽 zwwill
首發地址:zwwill/blog#32