聚焦于 Web 性能指標(biāo) TTI
在優(yōu)化網(wǎng)站性能的過程中,我們經(jīng)常遇到一個“為指標(biāo)而優(yōu)化”的困境。指標(biāo)并不能真正反映用戶體驗,而應(yīng)該最真實地反映用戶行為。
在本節(jié)中,我們將研究 TTI(Time to Interactive)。在深入探討這個話題之前,我們先了解一些背景知識。
RAIL 模型
RAIL 是一個以用戶為中心的性能模型。每個 web 應(yīng)用程序在其生命周期中都有四個不同的方面,這些方面以不同的方式影響性能:
圖片
1.響應(yīng):輸入延遲時間(從按下到繪制)小于 100 毫秒。
- 用戶按下一個按鈕(例如打開導(dǎo)航)。
2.動畫:每幀工作的完成時間(從 JS 到繪制)小于 16 毫秒。
- 用戶滾動頁面,拖動手指(例如打開菜單)或看到動畫。當(dāng)拖動時,應(yīng)用程序的響應(yīng)應(yīng)該與手指位置相關(guān)(例如下拉刷新,滑動輪播)。此指標(biāo)僅適用于拖動的連續(xù)階段,而不適用于初始階段。
3.空閑:主線程 JS 工作被分成不超過 50 毫秒的塊。
- 用戶不與頁面交互,但主線程應(yīng)有足夠的時間處理下一個用戶輸入。
4.加載:頁面可以在 1000 毫秒內(nèi)準(zhǔn)備就緒。
- 用戶加載頁面并看到關(guān)鍵路徑內(nèi)容。
如果你想提高網(wǎng)站的用戶體驗,RAIL 是一個很好的評估模型。
解釋 TTI(Time to Interactive)
TTI 是指應(yīng)用程序已經(jīng)可視化渲染并且可以響應(yīng)用戶輸入的時間。為了理解 “TTI”,我們需要了解它的計算規(guī)則。我們看看下圖:
圖片
在官方文檔中找到了以下描述:
First Idle 是主線程第一次靜止并且瀏覽器完成第一次有意義繪制的早期標(biāo)志。
Time to Interactive 是在第一次有意義繪制之后。瀏覽器的主線程已經(jīng)靜止至少 5 秒,并且沒有長任務(wù)會阻止立即響應(yīng)用戶輸入。
我們可以簡單地理解為:
First Idle 是主線程處于靜止?fàn)顟B(tài)并且瀏覽器完成了第一次有意義繪制的早期標(biāo)志;TTI 發(fā)生在 FMP 之后,瀏覽器的主線程保持空閑至少 5 秒,沒有任何可能阻止用戶交互響應(yīng)的“長任務(wù)”。
長任務(wù)
對于“長任務(wù)”,我們?nèi)鐖D所示:
圖片
對于用戶來說,長任務(wù)時間表現(xiàn)為卡頓或滯后,這也是當(dāng)前糟糕的網(wǎng)絡(luò)體驗的主要根源。
如何測量長任務(wù)?
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// TODO...
console.log(entry);
}
});
observer.observe({entryTypes: ['longtask']});
控制臺輸出如下:
{
"name": "self",
"entryType": "longtask",
"startTime": 315009.59500001045,
"duration": 99.9899999878835,
"attribution": [
{
"name": "unknown",
"entryType": "taskattribution",
"startTime": 0,
"duration": 0,
"containerType": "window",
"containerSrc": "",
"containerId": "",
"containerName": ""
}
]
}
長任務(wù) API 可以將任何超過 50 毫秒的任務(wù)標(biāo)記為潛在問題,并向應(yīng)用程序開發(fā)人員展示這些任務(wù)。選擇 50 毫秒是為了確保應(yīng)用程序滿足 RAIL 性能準(zhǔn)則,即在 100 毫秒內(nèi)響應(yīng)用戶輸入。
在實際開發(fā)中,我們可以使用一種 hack 方法來檢查頁面代碼中的“長任務(wù)”:
// 檢測長任務(wù) hack
(function detectLongFrame() {
let lastFrameTime = Date.now();
requestAnimationFrame(function() {
let currentFrameTime = Date.now();
if (currentFrameTime - lastFrameTime > 50) {
// 在這里報告長幀...
}
detectLongFrame(currentFrameTime);
});
}());
如何計算 TTI?
在計算之前,我們先看看 Timing API:
圖片
在官方的 Google 文檔中,有如下描述:
注意:DOM 交互完成后的最小 FMP 值 DOM 交互完成是所有 DOMContentLoaded 監(jiān)聽器執(zhí)行完畢的時間點。通常,頁面的關(guān)鍵事件監(jiān)聽器很少在此時間點之前安裝。我們實驗的一些 firstInteractive 定義只查看長任務(wù)和網(wǎng)絡(luò)活動(而不是查看安裝了多少事件監(jiān)聽器),有時在加載的前 5-10 秒內(nèi)沒有長任務(wù),我們會在 FMP 時觸發(fā) FirstInteractive,而此時網(wǎng)站通常還沒有準(zhǔn)備好處理用戶輸入。我們發(fā)現(xiàn),如果我們將 max(DOMContentLoadedEnd, firstInteractive) 作為最終的 firstInteractive 值,返回的值在合理范圍內(nèi)。等待 DOMContentLoadedEnd 來聲明 FirstInteractive 是合理的,因此所有下面介紹的定義都在 DOMContentLoadedEnd 時降低了 firstInteractive 的下限。
因此,我們可以大致估算使用 domContentLoadedEventEnd:
TTI: domContentLoadedEventEnd - navigationStart,
domContentLoadedEventEnd: 文檔 DOMContentLoaded 事件結(jié)束的時間。
domContentLoadedEventEnd 屬性必須返回一個具有時間值的 DOMHighResTimeStamp,該值等于當(dāng)前文檔的 DOMContentLoaded 事件完成后的時間。
如果你覺得上述計算過于復(fù)雜,可以使用 Google 提供的 Polyfill 來獲取。
TTI 指標(biāo)監(jiān)控
我們可以使用 Google TTI Polyfill 監(jiān)控 TTI。
npm install tti-polyfill
使用
import ttiPolyfill from './path/to/tti-polyfill.js';
ttiPolyfill.getFirstConsistentlyInteractive(opts).then((tti) => {
// 使用 `tti` 值進行一些操作。
});
結(jié)論
通過研究 TTI,我們可以更好地理解如何提高網(wǎng)頁的交互性能。RAIL 模型為評估用戶體驗提供了一個框架,而 TTI 則是衡量網(wǎng)頁何時可以交互的重要指標(biāo)。通過檢測和優(yōu)化長任務(wù),我們可以顯著改善用戶體驗,并確保網(wǎng)頁在加載后盡快變得可交互。