效能優化之詳解各種指標

2023-07-10 12:00:37

前言

上篇文章最後提到了我們可以通過performance的一些屬性對效能做統計,我們會發現performance物件下有非常多的屬性,遠不止上篇文章提到的DOMContentLoadedLoad這兩個事件。

或許你在瀏覽器控制檯見過它們這些身影:DCLLCPFPFCPL

這裡的DCLL就是我們上篇文章介紹的DOMContentLoadedLoad這兩個事件,那剩下的LCPFPFCP又分別代表什麼呢?

在早期前端三劍客的時代或者現在的伺服器端渲染,DCLL確實可以很好地衡量首屏內容展示時間,但對於現代各種框架盛行的單頁應用,由於都是通過JS操作DOM向頁面新增主要內容,DCLL事件就不能很好地衡量首屏顯示時間了。

於是有FP、FCP、FMP被提出來,它們關注的不是載入,而是渲染,因此能更好地表現使用者看到的情況。

FP、FCP這兩個指標雖然表達了渲染的事件,但對「使用者關注的內容」沒有體現,比如首屏渲染出來一個背景,或者一個loading,可能對於使用者來說和白屏區別不大。FMP雖然體現了「關鍵內容」的要素,但它是複雜的、模糊的,甚至是錯誤的,並不能準確識別頁面主要內容的載入時機。

後來LCP指標被提出來,表示「用於度量視口中最大的內容元素何時可見」,它用來代替FMP,表徵頁面的關鍵元素何時可以被使用者看到。

除了載入效能,還有可互動時間、穩定性指標、流暢性指標,在不同的業務場景都可以被監控用來作為提升使用者體驗的依據。

效能相關

上面我們提到了各種效能相關的事件,那麼它們各種代表的含義是什麼呢?

關鍵事件

名詞 全稱 解釋
FP firstPaint 首次繪製
FCP firstContentfulPaint 首次內容繪製
LCP largestContentfulPaint 最大內容繪製
DCL domContentLoaded dom內容解析完成
L loaded 頁面的load事件

DCL(DOMContentLoaded)

當初始的 HTML 檔案被完全載入和解析完成之後,DOMContentLoaded 事件被觸發,而無需等待樣式表、影象和子框架的完全載入。

事件監聽

document.addEventListener('DOMContentLoaded', (event) => {
    console.log('DOM 完全載入以及解析')
});

耗時計算

performance.timing.domContentLoadedEventStart - performance.timing.fetchStart

Load

load 事件在整個頁面及所有依賴資源如樣式表和圖片都已完成載入時觸發。它與 DOMContentLoaded 不同,後者只要頁面 DOM 載入完成就觸發,無需等待依賴資源的載入。

事件監聽

window.addEventListener('load', (event) => {
  console.log('頁面載入完成');
});

耗時計算

performance.timing.loadEventEnd - performance.timing.fetchStart

DCL與Load觸發的先後順序

很多人可能會誤以為Load的觸發一定會在DCL之後,雖然絕大多數我們看到的確實是這樣,但你從兩者的MDN解釋上來看,DCL關注的時HTML檔案的載入與解析,而Load只關注資源的載入,所以兩者觸發的先後順序並不是絕對的。

比如下面兩種情況:

第一種: 頁面非常簡單,沒有引入任何外部資源

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div class="container">
        
    </div>

</body>
</html>

從圖中可以看到此時的Load觸發在DCL之前,這是因為load不包含對檔案解析的時間

第二種: 我們引入一張圖片

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div class="container">
        <img src="https://imgservices-1252317822.image.myqcloud.com/coco/s06012023/9ac85415.g0q5wz.png" class="zan_icon" />
    </div>

</body>
</html>

從這張圖上我們可以看到此時DCLLoad先觸發,並且很明顯可以看到在兩者之前多了個圖片的下載過程。

所以兩者觸發的先後順序並不是固定的,如果頁面有許多外部資源需要載入,那麼load 事件會後觸發,如果頁面內容較多,外部資源較少,那麼load事件可能先觸發。

為了應對現在框架盛行的單頁應用新增了下面這幾個指標,它們關注的不再是頁面的載入過程,而是頁面的渲染過程。

FP與FCP

為了方便,這兩個放一起講:

FP,全稱 First Paint, 代表首次渲染的時間點,即首次視覺變化發生的時間點。前端開發者經常談到的白屏時間(使用者看不到任何內容)就是使用者存取網頁到 FP 的這段時間

FCP,全稱 First Contentful Paint,代表首次 DOM 內容 渲染的時間點DOM 內容 可以是文字、影象(包括背景影象)、<svg>元素或非白色的 <canvas> 元素。

簡單點理解就是FCP事件指渲染出第一個內容的事件,而FP指渲染出第一個畫素點,渲染出的東西可能是內容,也可能不是。

⚠️需要注意的是,FCP一定不會比FP晚觸發,但可能會一起觸發,絕大多數情況是FP在FCP之前觸發!

幾種場景

第一種: 無FP

是不是很奇怪,怎麼會有這種情況?其實我們上面DCLLoad那裡的第一個案例就出現了這種情況

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div class="container">
        
    </div>

</body>
</html>

頁面上有節點但沒有樣式,很顯然這種情況是不需要渲染頁面的,所以也就沒有FP

當然這裡需要注意的是這裡的節點不包括一些自身可見的節點(比如img、input、video等)

還有一種情況就是如果要渲染的內容在視口之外,那麼也不會觸發 FP

第二種: 有FP無FCP

同時為了驗證第一種說法的注意點,這裡我就寫一個input來試試

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div class="container">
        <input type="text" />
    </div>

</body>
</html>

從這裡我們就能發現,此時頁面只會觸發FP,因為沒有內容

耗時計算

// FP
const fp = performance.getEntries('paint').filter(entry => entry.name == 'first-paint')[0].startTime;

// FCP
const fcp = performance.getEntries('paint').filter(entry => entry.name == 'first-contentful-paint')[0].startTime;

FP與DCL的觸發順序

瀏覽器不一定等到所有的DOM都解析完再開始渲染,如果DOM節點少,瀏覽器會載入完再渲染,但是如果節點很多,瀏覽器解析一部分節點後就會開始渲染(這時候就會觸發FP)。也就是說,當需要渲染的節點數少的時候,DCL會在FP前面;當需要渲染的節點數很多時候,DCL會在FP後面。

現在來說,絕大部分的專案都是FPDCL之前觸發,這樣使用者可以更快的看到頁面內容。

LCP

LCP,全稱 Largest Contentful Paint,根據頁面首次開始載入的時間點(即 first started loading,可以通過 performance.timeOrigin 得到)來報告可視區域內可見的最大影象或文字塊完成渲染的相對時間

LCP評分

為了提供良好的使用者體驗,我們應努力將最大內容繪製時間控制在2.5 秒或更短。

LCP包含哪些元素型別

  • <img>元素
  • <image><svg>元素內的元素
  • <video>帶有海報影象的元素(使用海報影象載入時間)
  • 具有通過函數載入的背景影象的元素url()(而不是CSS 漸變)
  • 包含文位元組點或其他內聯級文字元素子元素的塊級元素。

隨著更多研究的進行,未來可能會新增其他元素

如何確定頁面的LCP元素

這裡可以通過performance面板,在Timings這一行找到LCP,點選它再找到下面的summary,就能找到LCP對應的節點元素了。

耗時計算

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    console.log('LCP candidate:', entry.startTime, entry);
  }
}).observe({type: 'largest-contentful-paint', buffered: true});

體驗相關

谷歌一直十分重視網站的使用者體驗,移動友好性,頁面載入速度和HTTPS是Google已經使用的頁面排名因素,而2020年,谷歌將Core Web Vitals新納入的使用者體驗指標。

TTI

TTI 全稱Time to Interactive 它用於測量頁面從開始載入到主要子資源完成渲染,並能夠快速、可靠地響應使用者輸入所需的時間。

測量步驟

測量TTI一般需要按以下步驟:

  1. 先進行首次內容繪製 (FCP)
  2. 沿時間軸正向搜尋時長至少為 5 秒的安靜視窗,其中,安靜視窗的定義為:沒有long task且不超過兩個正在處理的網路 GET 請求
  3. 沿時間軸反向搜尋安靜視窗之前的最後一個長任務,如果沒有找到長任務,則在 FCP 步驟停止執行
  4. TTI 是安靜視窗之前最後一個長任務的結束時間(如果沒有找到長任務,則與 FCP 值相同)

為了方便理解,可以對照這張圖

優秀指標

為了提供良好的使用者體驗,網站在普通移動硬體上進行測試時,應該努力將可互動時間控制在5 秒以內。

TBT

TBT全稱 Total Blocking Time, 它主要是用於度量 FCP 和 TTI 之間的總的阻塞時間。

只要存在long task(在主執行緒上執行超過 50 毫秒 (ms) 的任務),主執行緒就會被視為「阻塞」。我們說主執行緒被「阻塞」,因為瀏覽器無法中斷正在進行的任務。因此,如果使用者在長時間任務中確實與頁面進行互動,則瀏覽器必須等待任務完成才能響應*。*

優秀指標

測量TBT可以使用谷歌Lighthouse

為了提供良好的使用者體驗,在平均移動硬體上進行測試時,網站應努力使總阻塞時間低於200 毫秒

CLS

CLS 全稱 Cumulative Layout Shift 累積佈局偏移 ,它用來測量整個頁面生命週期內發生的所有意外佈局偏移中最大一連串的佈局偏移分數

CLS詳情

只要可視區域中可見元素的起始位置(例如,元素在預設書寫模式下的頂部和左側位置)在兩幀之間發生了變更,該 API 就會報告layout-shift條目。這樣的元素被視為不穩定元素

請注意,只有當現有元素的起始位置發生變更時才算作佈局偏移。如果將新元素新增到 DOM 或是現有元素更改大小,則不算作佈局偏移,前提是元素的變更不會導致其他可見元素的起始位置發生改變。

CLS分數

瀏覽器在計算佈局偏移分數時,會檢視可視區域大小和兩個已渲染幀之間的可視區域中不穩定元素的位移。佈局偏移分數是該位移的兩個度量的乘積:影響分數距離分數(兩者定義如下)。

佈局偏移分數 = 影響分數 * 距離分數

優秀指標

為了提供良好的使用者體驗,網站應該努力將 CLS 分數控制在0.1 或以下。為了確保您能夠在大部分使用者的存取期間達成建議目標值,一個良好的測量閾值為頁面載入的第 75 個百分位數,且該閾值同時適用於移動和桌面裝置。

這裡需要注意的是在寫動畫時優先考慮使用CSS transform屬性,因為它能夠在不觸發佈局偏移的情況下為元素設定動畫:

  • transform: scale()來替代和調整heightwidth屬性。
  • 如需使元素能夠四處移動,可以用transform: translate()來替代和調整toprightbottomleft屬性。

web-vitals

為了更準確的統計效能資料,可以使用web-vitals庫來測量各項效能指標

可以獲取的指標有:CLS、FID、LCP、以及 FCP、TTFB

import {getCLS, getFID, getLCP} from 'web-vitals'

getCLS(console.log);
getFID(console.log);
getLCP(console.log);

如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖 第一時間獲取最新文章~