Coolkid mascot CoolkidLab Build in Public. Level up together.
首頁新手教學SEO 歷程開源工具庫陪跑服務關於

SEO · 效能 perf 戰役

PageSpeed 93→83 又修回:typewriter 遇 LCP

閱讀

為了讓 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 對話的打字機。

技術實作:

  1. 寫一個 typewriter.js 在 DOMContentLoaded 後 把 hero h1 內容 wrap 成 per char
  2. 每個 span CSS opacity:0 + translateY(8px) + animation-delay 遞增
  3. 用 IntersectionObserver 觀察 hero h1 → viewport 內 add .is-visible class 觸發 animation
  4. 考量到 hero h1 內含 包不下去 改用 element-level fade-in (opacity 0 → 1 + transition 0.45s)

效果上,hero h1 進場確實有淡入感,看起來還不錯。

但有一個副作用我完全沒想到,這個淡入會被 PageSpeed 算進 LCP。

2. PageSpeed 報告(加 typewriter 後)

PageSpeed 加 typewriter 後 行動裝置 83 分 LCP 4.0s 紅燈
加 typewriter 後 - 效能 83 / FCP 2.2s 橘 / LCP 4.0s 紅 / TBT 100ms / CLS 0.059

改完之後:效能 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 被打字機接管之後的時序是這樣:

  1. 頁面 load 完 hero h1 文字在 DOM 但 opacity:0(看不見)
  2. 等 DOMContentLoaded + JS init(~200-300ms)
  3. IntersectionObserver callback 觸發 add .is-visible class
  4. transition opacity 0 → 1 過 0.45s
  5. 最終 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 個坑:

  1. opacity 0 → 1 fade-in — LCP 算「最終 pixel」 fade 過程都不算 done
  2. transform translate / scale 進場 — 同上 final transform 達成才算 done
  3. JS 延遲注入內容 — 例如 React 渲染前的 placeholder 也會被算 LCP 直到真實內容 mount

在「想看動畫」跟「LCP」這對矛盾之間,通常要選 LCP。

動畫是加分項,不是必需品。

效能是排名的次要訊號,也直接影響體驗。動畫只是加分項。

5. 拿掉 typewriter 完整 step(5 分鐘)

  1. 刪 assets/typewriter.js 檔案
  2. 拿掉 build.py 內 TYPEWRITER_JS_V 變數 +