「前端那些事兒」⑤ 定位性能指標FMP

什麼是FMP?

可能你們對「白屏時間」這個名詞並不陌生,他是「刀耕火種」年代,咱們收集的頁面性能指標之一,隨着前端工程的複雜化,白屏時間已經沒有什麼實質性的意義了,取而代之的就是 FMP。前端

先來介紹幾個與之相關的名詞。node

  • FP(First Paint):首次繪製,標記瀏覽器渲染任何在視覺上不一樣於導航前屏幕內容的時間點
  • FCP(First Contentful Paint):首次內容繪製,標記的是瀏覽器渲染第一針內容 DOM 的時間點,該內容多是文本、圖像、SVG 或者 <canvas> 等元素
  • FMP(First Meaning Paint):首次有效繪製,標記主角元素渲染完成的時間點,主角元素能夠是視頻網站的視頻控件,內容網站的頁面框架也能夠是資源網站的頭圖等。

相對於 FP 和 FCP,FMP 是咱們前端最常關注的重要性能指標,Google 定義它爲「是否有用?」的時間點。然而,「是否有用?」是很難以通用方式界定的,所以,至今依然沒有標準的 API 輸出。git

社區中常有這麼幾種方式進行「相對準確」的計算 FMP,所謂相對準確,是相對於實際項目而言。github

  1. 主動上報:開發者在相應頁面的「Meaning」位置上報時間
  2. 權重計算:根據頁面元素,計算權重最高的元素渲染時間
  3. 趨勢計算:在 render 期間,根據 dom 的變化趨勢推算 FMP 值

本文將着重介紹第二種方式。算法

權重定位

所謂權重,即,將頁面的元素以約定的「權重比」遍歷出「權重值」最大的某一個或一組 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 // 整個子樹的全部節點
});
複製代碼

下圖粗濾的解析了正常單頁面的渲染過程

  • 預備階段:導航階段,處在鏈接相應的過程
  • 階段一:首字節渲染階段,也是FCP,DOM 樹的第一次有效變化
  • 階段二:基本框架渲染完成
  • 階段三:獲取到數據,渲染到視圖上
  • 階段四:圖片加載完成,加載過程不被標記

實際上在第1、第三階段之間還存在着大量的 DOM 變化,Mutation Observer 事件的觸發並非同步的,而是異步觸發的,也就是說,等到當前「階段」全部 DOM 操做都結束才觸發。

Mutation Observer 有如下特色

  • 它等待全部腳本任務完成後,纔會運行(即異步觸發方式)。
  • 它把 DOM 變更記錄封裝成一個數組進行處理,而不是一條條個別處理 DOM 變更。
  • 它既能夠觀察 DOM 的全部類型變更,也能夠指定只觀察某一類變更。

load 事件觸發後,各個階段的 tag 已經被打到標籤上了

此處以『_ti』昨晚標記 key。

在打標記的同時,須要記錄下當前的時間節點,備用

// 僞代碼
function callback() {
    global.timeStack[++_ti] = performance.now(); // 記時間
    doTag(_ti); // 打標記
}
複製代碼

標記打完後就等 load 的那一刻進行計算反推了。

計算權重值

通常來講

  • 視圖佔比越大的元素越有多是主角元素
  • 視頻比圖片更多是主角元素
  • svgcanvas 也很重要
  • 其餘元素均可以按普通 dom 計算了
  • 背景圖片視狀況而定,可記可不記
第一步:簡單粗暴,按大小計算
// 僞代碼
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

相關文章
相關標籤/搜索