拼手機 Lighthouse 100:LCP、CLS 與 Speed Index 怎麼互相拉扯

讀經站衝手機實驗室分數時實際踩過的坑:哪些改動有效、哪些反而更糟,最後怎麼收斂。

日期 2026-07-27標籤 效能, lighthouse, web-vitals相關作品 bible-good-reading

背景

聖經好閱讀版 是靜態站加一層閱讀器:建置時產出 HTML/JSON,瀏覽器裡負責虛擬章節窗、搜尋、工具列。電腦上跑 Lighthouse 通常很漂亮;換成手機 + 模擬慢速網路,分數與體感差很多。

我當時的目標很明確:手機实验室 Performance 想碰 100。下面依時間線寫我們實際改過什麼,以及為什麼後來又撤回。

一開始其實還行的東西

這些後來都還留著,因為副作用小、收益清楚:

建置產物壓縮。 site/ 裡的 CSS/JS 保持可讀;寫進 dist/ 前再 minify。上傳體積小一截,原始碼也不用為了分數變難維護。

少觸發強制重排。 報告裡若出現大段 reflow,常見原因是捲動時一直 getBoundingClientRectgetComputedStyle。我們拿掉「每一幀都量一次章節高度」、章節 margin 改快取、字級第一次寫入時可略過一次重量排。數字上 TBT/主執行緒時間會比較老實。

別讓平台多塞一支腳本。 關於頁若出現明文 mailto:,Cloudflare 有時會注入 email 混淆用的 JS。改成頁面上只放 data-*,進站後再組信箱,HTML 乾淨一點。

到這裡,分數有進步,但首屏「經文何時出現、會不會跳一下」仍不穩——那才是後面大部分時間花掉的地方。

首屏:介紹、載入中、經文,只能選一個故事

閱讀器殼層 HTML 裡,曾經有一段首頁介紹(站名、功能說明、連到書卷目錄)。經文則多半要等 JS fetch JSON 再畫進 #read-main

於是首屏會變成好幾種節奏:

  1. 先看到介紹,JS 一跑就清成「載入中…」,再換成經文。
    人眼在手機上常常只看到閃一下;CLS 通常還好(介紹沒待久),但 LCP 很難吃到介紹,只能等後面的經文。

  2. 刻意留著介紹,等經文好了再整塊換掉。
    介紹有機會當早期 LCP,實驗室 LCP 看起來好看一點;換上虛擬章節時,#read-main 高度大變,CLS 很容易衝過 0.1。

  3. 用 CSS 技巧「先撐住版面」。
    例如幫主區加 min-height: 100svh,或先寫死 style.minHeight 再在下一幀清掉。聽起來像在防位移,實務上常更糟:CLS 量的是已出現的元素起點有沒有被推走,不是「外框有沒有留白」。手機網址列伸縮時,svh 自己還會變。這類改動我們後來整段撤掉。

  4. 關鍵 CSS inline,完整 CSS 非阻塞載入。
    首屏骨架先出來,細節樣式後到。按鈕尺寸、字級、間距一補上,已經排好的經文列會再擠一次——我們就遇過 CLS 報在 #read-main,根因是節上的複製/朗讀按鈕在關鍵 CSS 裡沒預留尺寸。即使補齊按鈕尺寸,兩段樣式接力對閱讀站仍偏脆。整份 CSS 塞進 HTML 也曾試過:請求少了,HTML 變胖,LCP 反而差。

結論很土:介紹長駐讀經區對 SEO 敘事有幫助,對「打開就讀」與 CLS 不友善;過早清空則傷害 LCP。 兩邊靠 CSS 魔術很難同時滿分。

比較有效的一刀:HTML 裡先有經文,但不要太多

後來首頁改成建置時把創世記開頭幾章直接寫進 index.html(含虛擬捲動用的上下 spacer),並標 data-ssr-home。JS 若發現目標章已在 DOM,就 hydrate:同步虛擬範圍、量測已渲染章節、不要先 innerHTML 清掉。其餘章再用既有的 append/視窗邏輯補,而不是整窗 setVirtualWindow 重刷(重刷等於把剛畫好的首章拆掉重來,CLS/體感都會回去)。

這對 LCP/CLS 幫助很大:首屏主內容不再綁在 fetch 後面。

接著 Speed Index 變差。原因也很直接——SI 看的是視窗畫面多久「看起來填滿」。一次塞進約九章時,index.html 來到將近 200KB,解析與排版變重,SI 從「還行」掉到比較難看的區間;LCP/CLS 卻仍不錯。這是典型的用首包換穩定繪製過頭。

收斂做法:首頁 SSR 只留兩章(創 1、創 2),體積回到約 70KB 等級;hydrate 後背景再 append 補視窗。實驗室上 SI 有拉回來,CLS 仍可維持在 0 附近。章數可以再調,但方向是:夠首屏讀、不夠拖垮下載與繪製。

書籤/上次讀到別卷時,SSR 窗對不上目標章,仍應清成載入中再走原 fetch,避免「畫面上還是創世記、邏輯已在出埃及」的錯位。

實作時幾個容易寫錯的細節

Hydrate 條件不要寫死「必須等於完整視窗」。
閱讀器平常視窗可到十幾章;SSR 若只嵌兩章,卻要求 domMatchesBuffer(完整 radius),hydrate 永遠失敗,最後還是清畫面重抓,等於白做 SSR。

開目錄的按鈕若與「點頂欄即關抽屜」共用同一輪 click。
openSheet(),同一事件冒泡到 document,頂欄被當成「點外面」又 closeSheet()——使用者會覺得目錄壞掉。觸發鈕要排除在關閉邏輯外。

章標題做成整塊 button 開目錄。
桌面好像方便,手機 sticky 標題極容易誤觸,等於讀經時一直被工具抽屜打斷。目錄應留在明確的「目錄」控制,不要綁在標題上。這與分數無關,但和「效能優化順手改 UX」很有關。

怎麼理解三個數字(寫給之後的自己)

指標 它在問什麼 這站最容易怎樣被搞砸
CLS 已看見的東西有沒有被擠走 介紹↔經文整塊替換;樣式分兩段載入改了列高
LCP 最大那塊內容何時穩定出現 過早清介紹;經文只活在 fetch 之後
Speed Index 畫面多久看起來填滿 首屏 HTML/文字節點一次塞太多

它們不是同一題的三個分數。優化前先問:我這次到底在修哪一題?

結語

最後能靠近手機 Performance 滿分,靠的不是單一神奇設定,而是停掉幾件「聽起來很效能」的事,再加上少量首屏經文 + hydrate。分數會抖(尤其 TBT),同一版連跑兩次差一兩分很常見;比較值得盯的是:首屏是不是已經是經文、換章時會不會跳、HTML 有沒有無腦變胖。

相關作品頁:/projects/bible-good-reading/
線上:https://bible.succ.work

← 全部文章