精確說出每個指標量的是什麼、從哪裡量到哪裡、門檻是多少。
知道哪些指標已經被淘汰(FMP、FID)、被什麼取代、以及為什麼。
分辨 Core Web Vitals 與「診斷用指標」,並知道哪些只能在真實使用者環境量測。
能在自己的頁面上把這些指標印出來。
把指標放到時間線上
圖 1.1 將本章所有指標放在同一條頁面生命週期時間線上。左半部(TTFB、FP、FCP、LCP)是「載入」階段的時間點;INP 是整段互動期間所有互動延遲的彙整;CLS 則是整個生命週期中版面跳動的累計。
max width=
TTFB:Time to First Byte
從導覽開始(使用者按下 Enter 或點擊連結)到收到 HTML 回應的第一個位元組的時間。依 web.dev 的定義,它包含:重導時間、Service Worker 啟動時間、DNS 查詢、TCP 與 TLS 連線建立、送出請求到首位元組抵達之間的等待(伺服器處理時間)。
門檻: \(\leq 800\) ms; 800–1800 ms; \(> 1800\) ms(以 p75 計)。
性質:不是 Core Web Vitals,而是最重要的診斷指標。TTFB 是 FCP 與 LCP 的下限:HTML 還沒到,什麼都畫不出來。
對 SPA 尤其關鍵:純客戶端渲染的頁面拿到 HTML 後還要下載 JS、執行、抓資料才有內容,所以 TTFB 每慢 1 ms,後面全部順延。反過來,伺服器端渲染的頁面 TTFB 可能較高(伺服器要組 HTML),但 FCP / LCP 反而更早——這就是為什麼 web.dev 說 TTFB 門檻是「粗略指引」,要和你的架構一起看。
量測:所有主流瀏覽器都支援,取 ()。已把 TTFB 拆解到 RTT 層級。
FP:First Paint
瀏覽器第一次在畫面上渲染任何東西的時間,排除預設的白色背景,但包含非預設的背景色與 iframe 的外框。由 Paint Timing API 以 這個 entry 提供。
FP 回答的是「有在動嗎?」的最低門檻:使用者看到畫面從空白變成了「有東西」。規範將回報 FP 列為選擇性(Safari 就不回報 FP,只回報 FCP),且它沒有官方的良好 / 不佳門檻,主要用於診斷。在大多數頁面上 FP 與 FCP 幾乎同時,若兩者差距大,通常表示頁面先畫了一個有色背景或骨架,但真正的內容還在等(例如等待網頁字型或 JS)。
FCP:First Contentful Paint
從導覽開始到瀏覽器第一次渲染出任何 DOM 內容的時間。「內容」包括:文字(含仍在等待網頁字型的文字不算,需實際畫出)、、、非白色的 、CSS 背景圖片。iframe 內的內容不計入。
門檻: \(\leq 1.8\) s; 1.8–3.0 s; \(> 3.0\) s。
性質:不是 Core Web Vitals,但是 Lighthouse 效能分數的組成之一(權重 10%),也是 CrUX 中回報的指標。
常見瓶頸:TTFB 高; 內的 render-blocking CSS 與同步 JS;網頁字型以 (FOIT)方式載入導致文字遲遲不畫;SPA 要等 JS 執行才有內容。
量測:Paint Timing API 的 entry;Chromium、Firefox、Safari 都支援。
FMP:First Meaningful Paint(已淘汰)
Chrome 早期提出的啟發式指標,定義為「視窗上方(above-the-fold)最大的版面變化發生之後、且網頁字型已載入」的那次繪製,試圖回答「有用了嗎?」。
FMP 的問題在於它依賴 Chrome 渲染引擎的內部實作(統計 layout object 的數量變化),對頁面載入的微小差異極度敏感,常出現雙峰(bimodal)分布,且無法標準化、其他瀏覽器無法實作。Lighthouse 6.0(2020 年 5 月)將它棄用,並由 接替「主要內容何時出現」這個問題;後續版本已完全移除。
許多 2020 年前後撰寫的效能書籍仍花不少篇幅介紹 FMP、TTI 與 FID。閱讀時請自動把「FMP」對應到今天的「LCP」、「FID」對應到「INP」,並記得 TTI 也已在 Lighthouse 10 移除。這些指標背後的「問題」沒有變,變的是回答問題的量測方法。
LCP:Largest Contentful Paint
從導覽開始到視窗內最大的圖片、文字區塊或影片完成渲染的時間。它是「主要內容是否已呈現」的代理指標,屬於 Core Web Vitals。
哪些元素會被視為 LCP 候選?
依 Largest Contentful Paint API 規範與 web.dev 的整理:
元素(動畫圖片如 GIF 以第一幀呈現時間計)。
內的 元素。
元素:取 poster 圖片載入時間或影片第一幀呈現時間,較早者為準。
以 CSS 載入背景圖的元素(CSS 漸層不算)。
包含文字節點或行內文字子元素的區塊級(block-level)元素。
排除規則(Chromium)
的元素(使用者看不到)。
覆蓋整個視窗的元素(通常是背景而非內容)。
低資訊熵的圖片:自 Chrome 112 起,圖片的「編碼位元數 ÷ 顯示像素數」低於 0.05 bits/pixel 者不列入,用來排除純色佔位圖、極度模糊的 LQIP 與大面積漸層圖,避免有人用超小的假圖「作弊」。
文字元素只計算包住所有文字節點的最小矩形;所有元素都不計 margin、padding、border。
元素的大小以視窗內可見部分計;超出視窗或被裁切的部分不算。圖片若被放大顯示,以原始尺寸與顯示尺寸的較小者計。
LCP 如何被「決定」?
瀏覽器在載入過程中會持續回報新的、更大的候選元素(每個都是一筆 entry);最後一筆就是 LCP。一旦使用者與頁面互動(點擊、觸控、按鍵、捲動),瀏覽器就停止回報,因為互動之後的內容變化不再算「載入」。因此在 RUM 中要在頁面隱藏()或互動時才送出最終值。
門檻: \(\leq 2.5\) s; 2.5–4.0 s; \(> 4.0\) s。
支援:Chromium 系瀏覽器;Firefox 自 122 版起支援 LCP API;Safari 不支援。
會把 LCP 拆成四個子部分(TTFB、資源載入延遲、資源載入時間、元素渲染延遲)逐一優化。
FID:First Input Delay(已由 INP 取代)
使用者第一次與頁面進行離散互動(點擊、觸控、按鍵;不含捲動與縮放)時,從輸入發生到瀏覽器開始執行事件處理器之間的延遲。它只量「輸入延遲」,不含事件處理時間,也不含後續渲染。
門檻(歷史): \(\leq 100\) ms; \(> 300\) ms。
為什麼被取代:(1) 只看第一次互動,但使用者在頁面上的大部分互動發生在載入完成後;(2) 只量延遲那一小段,事件處理器本身跑了 500 ms、畫面更新又拖了 200 ms 都不算;(3) 到 2023 年超過九成網站都「通過」FID,指標已失去鑑別力。
Google 於 2024 年 3 月 12 日正式以 INP 取代 FID 成為 Core Web Vitals; 套件 v5 已移除 。
INP:Interaction to Next Paint
觀察頁面整個生命週期中所有點擊、觸控與鍵盤互動的延遲,回報其中接近最差的一個值(互動次數少於 50 次時取最大值;之後每 50 次互動忽略一個最高值,約等於第 98 百分位)。單次互動的延遲是從使用者輸入開始,到瀏覽器畫出下一個包含互動結果的畫格為止。
什麼算一次「互動」?
一次互動是由同一個使用者動作觸發的一組事件:
滑鼠點擊: \(\rightarrow\) \(\rightarrow\) 。
觸控: \(\rightarrow\) (以及可能的 )。
鍵盤: \(\rightarrow\) (輸入文字時每個按鍵都是一次互動)。
同一次互動裡多個事件的延遲取最長者,不是相加。hover 與捲動不算互動(捲動由瀏覽器的合成執行緒處理,通常不受主執行緒阻塞影響)。
三個子部分
每次互動的延遲 = 輸入延遲(input delay,事件發生到處理器開始執行)+ 處理時間(processing duration,所有事件處理器執行完畢)+ 呈現延遲(presentation delay,處理完到下一個畫格上屏)。會針對這三段分別討論優化。
門檻: \(\leq 200\) ms; 200–500 ms; \(> 500\) ms。
性質:Core Web Vitals;只能在真實使用者環境量測(實驗室沒有真人互動)。實驗室以 TBT 作為代理,DevTools 的 Performance 面板則可在你手動互動時即時顯示每次互動的延遲。
支援:以 Event Timing API 為基礎,目前為 Chromium 專有。
目前 INP 是三個 Core Web Vitals 中最多網站無法通過的一項,主因是龐大的 JavaScript、第三方腳本與 hydration 造成的長任務。
CLS:Cumulative Layout Shift
衡量頁面生命週期中非預期版面跳動的嚴重程度。每次跳動有一個「版面偏移分數」(layout shift score);CLS 取最大的一段「跳動叢集」(session window)的分數總和。
單次版面偏移分數
\[\text{layout shift score} = \text{impact fraction} \times \text{distance fraction}\]
影響比例(impact fraction):前後兩個畫格中,所有「不穩定元素」佔據區域的聯集面積 ÷ 視窗面積。例如一個佔視窗 50% 高度的元素往下移了 25% 視窗高度,前後兩個位置的聯集是 75% \(\Rightarrow\) 0.75。
距離比例(distance fraction):不穩定元素在該畫格中移動的最大距離(水平或垂直)÷ 視窗的較長邊。上例為 0.25。
該次分數 \(= 0.75 \times 0.25 = 0.1875\)。
Session window 與排除規則
連續的跳動若彼此間隔少於 1 秒,會被歸入同一個 session window;單一 window 最長 5 秒。CLS = 所有 window 中分數總和最大者。這是 2021 年的修訂,目的是不讓長時間開著的頁面(如無限捲動)分數無限累積。
使用者輸入後 500 ms 內的跳動被視為「預期中的」(),不計入 CLS。但注意:只有離散輸入(點擊、觸控、按鍵)算;捲動、拖曳、縮放不算最近輸入。
只有起始位置改變才算跳動;新元素出現在原本空白處、或元素只改變尺寸但起點不動,都不算。以 做的動畫也不算(它不影響版面)。
門檻: \(\leq 0.1\); 0.1–0.25; \(> 0.25\)(無單位)。
性質:Core Web Vitals;實驗室與真實使用者皆可量,但實驗室通常只量到載入階段,真實使用者才會量到之後的互動與延遲載入內容。
支援:Layout Instability API,Chromium 專有。
補充:Lighthouse 中的其他實驗室指標
L3.2cm L7.4cm L3.6cm 指標 & 定義 & 狀態
Speed Index(SI) & 以逐幀截圖分析視覺內容「填滿」的速度,越早填滿越低。 & Lighthouse 分數組成,權重 10%。
Total Blocking Time(TBT) & FCP 到 TTI 之間,所有長任務(\(>\)50 ms)超過 50 ms 部分的總和。 & Lighthouse 分數組成,權重 30%;INP 的實驗室代理。
Time to Interactive(TTI) & 頁面「可靠地」可互動的時間點(FCP 後主執行緒安靜 5 秒)。 & Lighthouse 10 已移除,因與使用者感受相關性低。
Max Potential FID & 最長任務的長度,作為 FID 上限估計。 & Lighthouse 6.0 已移除。
Lighthouse 10 之後的效能分數權重為:FCP 10%、SI 10%、LCP 25%、TBT 30%、CLS 25%。請記得這是實驗室分數,與 CrUX 的真實使用者評分是兩套系統。
總整理
L1.3cm L3.8cm C1.6cm C1.6cm C1.4cm C1.5cm L2.7cm 指標 & 量什麼 & 良好 & 不佳 & CWV? & Lab / Field & 來源 API
TTFB & 導覽開始到 HTML 首位元組 & \(\leq 0.8\) s & \(> 1.8\) s & 否(診斷) & 兩者 & Navigation Timing
FP & 第一次畫出任何非預設像素 & – & – & 否 & 兩者 & Paint Timing
FCP & 第一次畫出 DOM 內容 & \(\leq 1.8\) s & \(> 3.0\) s & 否 & 兩者 & Paint Timing
FMP & 最大版面變化後的繪製 & – & – & 已淘汰 & Lab & Chrome 內部啟發式
LCP & 視窗內最大內容元素完成渲染 & \(\leq 2.5\) s & \(> 4.0\) s & 是 & 兩者 & LCP API
FID & 第一次互動的輸入延遲 & \(\leq 100\) ms & \(> 300\) ms & 已被 INP 取代 & Field & Event Timing
INP & 所有互動延遲的近最差值 & \(\leq 200\) ms & \(> 500\) ms & 是 & Field & Event Timing
CLS & 最大跳動叢集的分數總和 & \(\leq 0.1\) & \(> 0.25\) & 是 & 兩者 & Layout Instability
TBT & 長任務阻塞時間總和 & \(< 200\) ms & \(> 600\) ms & 否 & Lab & Long Tasks
web.dev 說明 Core Web Vitals 門檻的訂定原則有兩個:(1) 符合人類感知研究——例如 100 ms 內的回饋會被感知為「即時」,1 秒內的變化不會打斷思路;(2) 可達成性——「良好」門檻必須是當時約前 10% 的網站已能達到的水準,「不佳」門檻則對應大約後 30% 的網站。這些門檻自 2020 年公布、2024 年 INP 加入後至今(2026 年)未再調整。
課堂實作
不使用任何套件,直接用 PerformanceObserver(會詳細解釋):
// TTFB
const nav = performance.getEntriesByType('navigation')[0];
console.log('TTFB', Math.round(nav.responseStart), 'ms');
// FP / FCP
new PerformanceObserver((list) => {
for (const e of list.getEntries()) console.log(e.name, Math.round(e.startTime), 'ms');
}).observe({ type: 'paint', buffered: true });
// LCP:持續回報候選,最後一筆才是最終值
new PerformanceObserver((list) => {
const last = list.getEntries().at(-1);
console.log('LCP 候選', Math.round(last.startTime), 'ms', last.element, last.url);
}).observe({ type: 'largest-contentful-paint', buffered: true });
// CLS:只累加沒有最近輸入的跳動(此處為簡化版,未實作 session window)
let cls = 0;
new PerformanceObserver((list) => {
for (const e of list.getEntries()) if (!e.hadRecentInput) cls += e.value;
console.log('CLS 累計', cls.toFixed(4));
}).observe({ type: 'layout-shift', buffered: true });開啟 PageSpeed Insights,輸入一個流量足夠的網站(例如新聞或電商網站)。
上半部「探索實際使用者的體驗」是 CrUX 的 28 天 p75 數據,注意 LCP、INP、CLS 三項是否都為綠色。
下半部是 Lighthouse 實驗室分數。找出兩者不一致的地方(例如實驗室 LCP 很好但真實 LCP 不佳),討論可能原因:真實使用者的裝置與網路分布?快取狀態?地理位置?
切換「行動裝置 / 電腦」,觀察差異。Google 排名主要看行動版數據。
一個頁面的 TTFB 是 300 ms、FCP 是 2.9 s,中間差了 2.6 s。請列出至少三種可能造成這段差距的原因。
某電商首頁的 LCP 元素在 Chrome 中被判定為一個 40 KB 的橫幅圖片,但設計師把它換成一張 3 KB 的極度模糊漸層圖後 LCP 從 3.1 s 變成 1.2 s。這算改善嗎?Chrome 112 之後會怎麼處理?
為什麼 INP 要「每 50 次互動忽略一個最高值」而不是直接取最大值?
一個使用無限捲動的社群動態頁面,在 CLS 改為 session window 計算方式之前與之後,分數會有什麼不同?為什麼舊算法對這類頁面不公平?
為什麼 FID 幾乎每個網站都「通過」,卻仍有大量網站的 INP 不佳?兩者定義上的哪個差異造成這個現象?
載入階段的時間點指標依序為 TTFB \(\rightarrow\) FP \(\rightarrow\) FCP \(\rightarrow\) LCP,每一個都以前一個為下限。
Core Web Vitals 目前為 LCP(\(\leq\)2.5 s)、INP(\(\leq\)200 ms)、CLS(\(\leq\)0.1),以真實使用者 28 天 p75 評分,三項全達良好才算通過。
FMP 與 FID 已淘汰;讀舊書時請對應到 LCP 與 INP。TTI 也已從 Lighthouse 移除。
INP 與 FID 只能在真實使用者環境量測;TBT 是實驗室代理。LCP、CLS、INP 的 API 主要是 Chromium 專有,TTFB 與 FCP 則跨瀏覽器可用。
CLS 的公式是 impact fraction \(\times\) distance fraction,並以 1 秒間隔、5 秒上限的 session window 取最大叢集。