PageSpeed 93→83 又修回:typewriter 遇 LCP
文 / Coolkid發布:2026-05-19 · 最後更新:2026-06-11閱讀約 8 分鐘

為了讓 Lab 有點動感,我加了一個 hero 文字逐字淡入的打字機進場動畫。
結果 PageSpeed 行動裝置從 93 掉到 83,LCP 從 2.3 秒拖到 4.0 秒。會不會太離譜。
會不會太離譜。這是我的第一個念頭。
花 5 分鐘除錯,找到根本原因。打字機把 hero h1 設成 opacity:0,等 IntersectionObserver 觸發,再花 0.45 秒淡進來。
而 hero h1 剛好就是 LCP 元素。LCP 算的是「畫面達到最終樣子」的時間,opacity 從 0 到 1 的全程都算延遲。
拿掉打字機之後,效能回到 90 以上。
這篇紀錄完整的除錯過程、教訓,還有替代方案。給那些加了 JS 動畫、效能掉分卻不知道為什麼的非工程師。
1. 我加了什麼動畫
想做的效果是這樣。hero h1「AI SEO 新手成長」進頁面的時候,文字一個一個跳出來,像 AI 對話的打字機。
技術實作:
- 寫一個 typewriter.js 在 DOMContentLoaded 後 把 hero h1 內容 wrap 成 per char
- 每個 span CSS opacity:0 + translateY(8px) + animation-delay 遞增
- 用 IntersectionObserver 觀察 hero h1 → viewport 內 add .is-visible class 觸發 animation
- 考量到 hero h1 內含 包不下去 改用 element-level fade-in (opacity 0 → 1 + transition 0.45s)
效果上,hero h1 進場確實有淡入感,看起來還不錯。
但有一個副作用我完全沒想到,這個淡入會被 PageSpeed 算進 LCP。
2. PageSpeed 報告(加 typewriter 後)
改完之後:效能 83 分,FCP 2.2 秒橘燈,LCP 4.0 秒紅燈,TBT 100 毫秒綠燈,CLS 0.059 綠燈。
跟加打字機之前比,那時候效能 93 分、LCP 2.3 秒。現在 LCP 多了 1.7 秒,整體效能掉了 10 分。
這一改整整掉了 10 分。
3. 5 分鐘 debug:LCP 元素是什麼
PageSpeed 報告往下滑,會有一個「最大內容繪製元素」區塊,直接告訴你哪個元素是頁面的 LCP 候選。
我的 hero h1「AI SEO 新手成長」就是 LCP 元素。接著檢查它的 CSS:
我的 hero h1「AI SEO 新手成長」是 LCP 元素。檢查它的 CSS:
.typewriter-fade {
opacity: 0;
transform: translateY(4px);
transition: opacity 0.45s ease-out;
}
.typewriter-fade.is-visible {
opacity: 1;
transform: translateY(0);
}
問題就出在這一行 CSS。
LCP 算的是「最大元素達到最終顯示狀態」的時間。opacity 從 0 慢慢變 1,整段時間都被算進去。
hero h1 被打字機接管之後的時序是這樣:
- 頁面 load 完 hero h1 文字在 DOM 但 opacity:0(看不見)
- 等 DOMContentLoaded + JS init(~200-300ms)
- IntersectionObserver callback 觸發 add .is-visible class
- transition opacity 0 → 1 過 0.45s
- 最終 hero h1 pixel 達 opacity:1 → LCP 時刻
算下來,LCP 比沒有打字機、直接畫出來的情況,多了大約 1.5 到 2 秒。
4. 為什麼 JS 動畫遇到 LCP 元素要小心
LCP 是 Core Web Vitals 三大指標之一,另外兩個是 INP 跟 CLS。
Google 把 Core Web Vitals 當成頁面體驗的排名訊號之一,權重不大,也不及內容相關性。LCP 超過 4 秒算差,2.5 到 4 秒算中間,低於 2.5 秒才算好。
JS 動畫常見會踩到 LCP 的 3 個坑:
- opacity 0 → 1 fade-in — LCP 算「最終 pixel」 fade 過程都不算 done
- transform translate / scale 進場 — 同上 final transform 達成才算 done
- JS 延遲注入內容 — 例如 React 渲染前的 placeholder 也會被算 LCP 直到真實內容 mount
在「想看動畫」跟「LCP」這對矛盾之間,通常要選 LCP。
動畫是加分項,不是必需品。
效能是排名的次要訊號,也直接影響體驗。動畫只是加分項。
5. 拿掉 typewriter 完整 step(5 分鐘)
- 刪 assets/typewriter.js 檔案
- 拿掉 build.py 內 TYPEWRITER_JS_V 變數 +
