性能優(yōu)化之詳解各種指標
前言
上篇文章最后提到了我們可以通過「performance」的一些屬性對性能做統(tǒng)計,我們會發(fā)現(xiàn)performance對象下有非常多的屬性,遠不止上篇文章提到的DOMContentLoaded與Load這兩個事件。
或許你在瀏覽器控制臺見過它們這些身影:DCL、LCP、FP、FCP、L
這里的DCL與L就是我們上篇文章介紹的DOMContentLoaded與Load這兩個事件,那剩下的LCP、FP、FCP又分別代表什么呢?
在早期前端三劍客的時代或者現(xiàn)在的服務端渲染,DCL與L確實可以很好地衡量首屏內(nèi)容展示時間,但對于現(xiàn)代各種框架盛行的單頁應用,由于都是通過JS操作DOM向頁面添加主要內(nèi)容,DCL和L事件就不能很好地衡量首屏顯示時間了。
于是有FP、FCP、FMP被提出來,它們關(guān)注的不是「加載」,而是「渲染」,因此能更好地表現(xiàn)用戶看到的情況。
FP、FCP這兩個指標雖然表達了渲染的事件,但對“用戶關(guān)注的內(nèi)容”沒有體現(xiàn),比如首屏渲染出來一個背景,或者一個loading,可能對于用戶來說和白屏區(qū)別不大。FMP雖然體現(xiàn)了“關(guān)鍵內(nèi)容”的要素,但它是復雜的、模糊的,甚至是錯誤的,并不能準確識別頁面主要內(nèi)容的加載時機。
后來LCP指標被提出來,表示“「用于度量視口中最大的內(nèi)容元素何時可見」”,它用來代替FMP,表征頁面的關(guān)鍵元素何時可以被用戶看到。
除了加載性能,還有可交互時間、穩(wěn)定性指標、流暢性指標,在不同的業(yè)務場景都可以被監(jiān)控用來作為提升用戶體驗的依據(jù)。
性能相關(guān)
上面我們提到了各種性能相關(guān)的事件,那么它們各種代表的含義是什么呢?
關(guān)鍵事件
名詞 | 全稱 | 解釋 |
FP | firstPaint | 首次繪制 |
FCP | firstContentfulPaint | 首次內(nèi)容繪制 |
LCP | largestContentfulPaint | 最大內(nèi)容繪制 |
DCL | domContentLoaded | dom內(nèi)容解析完成 |
L | loaded | 頁面的load事件 |
DCL(DOMContentLoaded)
當初始的 「HTML」 文檔被完全加載和解析完成之后,DOMContentLoaded 事件被觸發(fā),而無需等待樣式表、圖像和子框架的完全加載。
事件監(jiān)聽
document.addEventListener('DOMContentLoaded', (event) => {
console.log('DOM 完全加載以及解析')
});
耗時計算
performance.timing.domContentLoadedEventStart - performance.timing.fetchStart
Load
「load」 事件在整個頁面及所有依賴資源如樣式表和圖片都已完成加載時觸發(fā)。它與 DOMContentLoaded 不同,后者只要頁面 DOM 加載完成就觸發(fā),無需等待依賴資源的加載。
事件監(jiān)聽
window.addEventListener('load', (event) => {
console.log('頁面加載完成');
});
耗時計算
performance.timing.loadEventEnd - performance.timing.fetchStart
DCL與Load觸發(fā)的先后順序
很多人可能會誤以為Load的觸發(fā)一定會在DCL之后,雖然絕大多數(shù)我們看到的確實是這樣,但你從兩者的MDN解釋上來看,DCL關(guān)注的時HTML文檔的加載與解析,而Load只關(guān)注資源的加載,所以兩者觸發(fā)的先后順序并不是絕對的。
比如下面兩種情況:
「第一種:」頁面非常簡單,沒有引入任何外部資源
<!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觸發(fā)在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>
從這張圖上我們可以看到此時DCL比Load先觸發(fā),并且很明顯可以看到在兩者之前多了個圖片的下載過程。
所以兩者觸發(fā)的先后順序并不是固定的,如果頁面有許多外部資源需要加載,那么load事件會后觸發(fā),如果頁面內(nèi)容較多,外部資源較少,那么load事件可能先觸發(fā)。」
為了應對現(xiàn)在框架盛行的單頁應用新增了下面這幾個指標,它們關(guān)注的不再是頁面的「加載」過程,而是頁面的「渲染」過程。
FP與FCP
為了方便,這兩個放一起講:
FP,全稱 「First Paint,」 代表「首次渲染的時間點」,即首次視覺變化發(fā)生的時間點。前端開發(fā)者經(jīng)常談到的「白屏時間」(用戶看不到任何內(nèi)容)就是「用戶訪問網(wǎng)頁到 FP 的這段時間」。
FCP,全稱 「First Contentful Paint」,代表「首次」 「DOM 內(nèi)容」 「渲染的時間點」,「DOM 內(nèi)容」 可以是文本、圖像(包括背景圖像)、<svg>元素或非白色的 <canvas> 元素。
簡單點理解就是FCP事件指渲染出第一個內(nèi)容的事件,而FP指渲染出第一個像素點,渲染出的東西可能是內(nèi)容,也可能不是。
需要注意的是,F(xiàn)CP一定不會比FP晚觸發(fā),但可能會一起觸發(fā),絕大多數(shù)情況是FP在FCP之前觸發(fā)!」
幾種場景
「第一種:」無FP
是不是很奇怪,怎么會有這種情況?其實我們上面DCL
與Load
那里的第一個案例就出現(xiàn)了這種情況
<!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>
頁面上有節(jié)點但沒有樣式,很顯然這種情況是不需要渲染頁面的,所以也就沒有FP
當然這里需要注意的是這里的節(jié)點不包括一些自身可見的節(jié)點(比如img、input、video等)
還有一種情況就是「如果要渲染的內(nèi)容在視口之外,那么也不會觸發(fā) 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>
從這里我們就能發(fā)現(xiàn),此時頁面只會觸發(fā)FP,因為沒有內(nèi)容
耗時計算
// 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的觸發(fā)順序
瀏覽器不一定等到所有的DOM都解析完再開始渲染,如果DOM節(jié)點少,瀏覽器會加載完再渲染,但是如果節(jié)點很多,瀏覽器解析一部分節(jié)點后就會開始渲染(這時候就會觸發(fā)FP)。也就是說,當需要渲染的節(jié)點數(shù)少的時候,DCL會在FP前面;當需要渲染的節(jié)點數(shù)很多時候,DCL會在FP后面。
現(xiàn)在來說,絕大部分的項目都是FP在DCL之前觸發(fā),這樣用戶可以更快的看到頁面內(nèi)容。
LCP
LCP,全稱 「Largest Contentful Paint」,根據(jù)頁面首次開始加載的時間點(即 「first started loading」,可以通過 performance.timeOrigin 得到)來報告可視區(qū)域內(nèi)可見的「最大圖像或文本塊」完成渲染的相對時間
LCP評分
為了提供良好的用戶體驗,我們應努力將最大內(nèi)容繪制時間控制在「2.5 秒」或更短。
??????
LCP包含哪些元素類型
- <img>元素
- <image>、 <svg>元素內(nèi)的元素
- <video>帶有海報圖像的元素(使用海報圖像加載時間)
- 具有通過函數(shù)加載的背景圖像的元素url()(而不是CSS 漸變)
- 包含文本節(jié)點或其他內(nèi)聯(lián)級文本元素子元素的塊級元素。
隨著更多研究的進行,未來可能會添加其他元素。
如何確定頁面的LCP元素
這里可以通過performance面板,在Timings這一行找到LCP,點擊它再找到下面的summary,就能找到LCP對應的節(jié)點元素了。
耗時計算
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
console.log('LCP candidate:', entry.startTime, entry);
}
}).observe({type: 'largest-contentful-paint', buffered: true});
體驗相關(guān)
谷歌一直十分重視網(wǎng)站的用戶體驗,移動友好性,頁面加載速度和HTTPS是Google已經(jīng)使用的頁面排名因素,而2020年,谷歌將Core Web Vitals新納入的用戶體驗指標。
TTI
TTI 全稱「Time to Interactive」 它用于測量頁面從開始加載到主要子資源完成渲染,并能夠快速、可靠地響應用戶輸入所需的時間。
測量步驟
測量TTI一般需要按以下步驟:
- 先進行「首次內(nèi)容繪制 (FCP)」
- 沿時間軸正向搜索時長至少為 5 秒的安靜窗口,其中,安靜窗口的定義為:沒有「long task」且不超過兩個正在處理的網(wǎng)絡 GET 請求
- 沿時間軸反向搜索安靜窗口之前的最后一個長任務,如果沒有找到長任務,則在 FCP 步驟停止執(zhí)行
- TTI 是安靜窗口之前最后一個長任務的結(jié)束時間(如果沒有找到長任務,則與 FCP 值相同)
為了方便理解,可以對照這張圖
優(yōu)秀指標
為了提供良好的用戶體驗,網(wǎng)站在普通移動硬件上進行測試時,應該努力將可交互時間控制在5 秒以內(nèi)。
TBT
TBT全稱 「Total Blocking Time」, 它主要是用于度量 FCP 和 TTI 之間的總的阻塞時間。
只要存在「long task」(在主線程上運行超過 50 毫秒 (ms) 的任務),主線程就會被視為“阻塞”。我們說主線程被“阻塞”,因為瀏覽器無法中斷正在進行的任務。因此,如果用戶在長時間任務中確實與頁面進行交互,則瀏覽器必須等待任務完成才能響應。
優(yōu)秀指標
測量TBT可以使用谷歌Lighthouse
為了提供良好的用戶體驗,在「平均移動硬件」上進行測試時,網(wǎng)站應努力使總阻塞時間低于「200 毫秒」。
CLS
CLS 全稱 「Cumulative Layout Shift」 累積布局偏移 ,它用來測量整個頁面生命周期內(nèi)發(fā)生的所有意外布局偏移中最大一連串的布局偏移分數(shù)。
CLS詳情
只要可視區(qū)域中可見元素的起始位置(例如,元素在默認書寫模式下的頂部和左側(cè)位置)在兩幀之間發(fā)生了變更,該 API 就會報告layout-shift條目。這樣的元素被視為不穩(wěn)定元素。
請注意,只有當現(xiàn)有元素的起始位置發(fā)生變更時才算作布局偏移。如果將新元素添加到 DOM 或是現(xiàn)有元素更改大小,則不算作布局偏移,前提是元素的變更不會導致其他可見元素的起始位置發(fā)生改變。
CLS分數(shù)
瀏覽器在計算布局偏移分數(shù)時,會查看可視區(qū)域大小和兩個已渲染幀之間的可視區(qū)域中不穩(wěn)定元素的位移。布局偏移分數(shù)是該位移的兩個度量的乘積:影響分數(shù)和距離分數(shù)(兩者定義如下)。
布局偏移分數(shù) = 影響分數(shù) * 距離分數(shù)
優(yōu)秀指標
為了提供良好的用戶體驗,網(wǎng)站應該努力將 CLS 分數(shù)控制在「0.1」 或以下。為了確保您能夠在大部分用戶的訪問期間達成建議目標值,一個良好的測量閾值為頁面加載的「第 75 個百分位數(shù)」,且該閾值同時適用于移動和桌面設備。
這里需要注意的是在寫動畫時優(yōu)先考慮使用CSS transform屬性,因為它能夠在不觸發(fā)布局偏移的情況下為元素設置動畫:
- 用transform: scale()來替代和調(diào)整height和width屬性。
- 如需使元素能夠四處移動,可以用transform: translate()來替代和調(diào)整top、right、bottom或left屬性。
web-vitals
為了更準確的統(tǒng)計性能數(shù)據(jù),可以使用web-vitals庫來測量各項性能指標
「可以獲取的指標有:CLS、FID、LCP、以及 FCP、TTFB」
import {getCLS, getFID, getLCP} from 'web-vitals'
getCLS(console.log);
getFID(console.log);
getLCP(console.log);