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 成 <span> per char
- 每個 span CSS opacity:0 + translateY(8px) + animation-delay 遞增
- 用 IntersectionObserver 觀察 hero h1 → viewport 內 add .is-visible class 觸發 animation
- 考量到 hero h1 內含 <span data-edit=...> 包不下去 改用 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 變數 + <script> 引用
- 拿掉 site.css 內 .tw-char / .typewriter / .typewriter-fade / @keyframes 規則
- python build.py 重新生成靜態 HTML
- git commit + push 觸發 Vercel deploy
- 等 1-2 分鐘 跑 PageSpeed 驗證
驗證之後,TBT 從 100 毫秒降到 40 毫秒,CLS 從 0.059 修到 0,直接合格。
但 Speed Index 還停在 4.4 秒,表示其他層面還有問題,可能是阻塞渲染、強制重排,或關鍵路徑太深。這部分會在 SEO Journey #11 完整修復。
整個打字機拿掉,加上等部署,總共 5 分鐘。
6. 教訓 3 條
- 任何 JS 動畫先想是否會碰到 LCP 元素。Hero 區的 h1 / 大圖 / 大文字 99% 是 LCP 候選。動畫加上去前先用 PageSpeed 看「LCP 元素」是哪個。
- IntersectionObserver 在 hero 區其實沒用。Hero 一定在初始 viewport 內 IO callback 一進頁面就 fire 等於沒 throttle。要省 IO 該用在 scroll 下面的元素。
- 「想看動畫」vs「LCP」要選一個。perf 是排名的次要訊號之一、也影響體驗 動畫是 nice-to-have。除非動畫對轉換有直接幫助 不然先選 perf。
7. 替代方案(如果還想要動畫)
如果動畫對你的網站確實重要,這裡有 3 個不會踩到 LCP 的方案:
- 只對非 LCP 元素加動畫 — Hero 下面的 card / section 用 IntersectionObserver fade-in OK 因為它們不是 LCP 候選
- 用 transform 而不是 opacity — 例如 hero 文字 translateY(20px) → 0 不影響 opacity 對 LCP 影響較小(但仍有 只是較輕)
- CSS first-paint animation 不用 JS — 例如 hero h1 { animation: fade-in 0.3s; } 直接寫 CSS 不靠 IO 等待 first paint 就跑完 LCP 影響最小
我自己最後的選擇是拿掉打字機,完全不放動畫,把效能跟無障礙擺前面。
這個取捨其實很清楚。
Lab 的特色是「進場一秒就可讀」,不是「進場很炫」。
Lab typewriter 動畫 debug 完整紀錄:加了 hero h1 逐字 fade-in 的 typewriter 後 PageSpeed 從 93 掉到 83、LCP 從 2.3s 拖到 4.0s。Root cause:LCP 元素(hero h1)被 typewriter 設 opacity:0 等 IO 觸發再 transition 0.45s LCP 算「最終 pixel」的時間直接被拖延 1.5-2 秒。修法:拿掉 typewriter(5 分鐘 step-by-step)。教訓 3 條:任何動畫先想是否碰 LCP 元素 / IO 在 hero 區沒用 / 動畫 vs LCP 要選一個。替代方案 3 個:只動畫非 LCP 元素 / 用 transform 不用 opacity / CSS first-paint animation 不用 JS。
名詞解釋
- 最大內容繪製(LCP, Largest Contentful Paint)
- 頁面上「最大那塊內容」(通常是首圖或大標題)出現所需的秒數。Google 標準 ≤ 2.5 秒。
- PageSpeed Insights
- Google 提供的免費網站速度體檢工具,輸入網址就給 0-100 分跟逐項改善建議。
- 累積版面位移(CLS, Cumulative Layout Shift)
- 畫面亂跳指數:載入過程版面移動越多、分數越高。標準 < 0.1,常見元兇是圖片沒寫尺寸跟字型替換。
- CSS
- 網頁的造型語言,管顏色、字體、排版跟動畫。
- 網站體驗核心指標(Core Web Vitals)
- Google 定的三項使用者體驗硬指標:LCP(載入速度)、CLS(畫面穩定)、INP(互動反應),會影響搜尋排名。
- 互動回應(INP, Interaction to Next Paint)
- 從你點按鈕到畫面有反應的延遲毫秒數,標準 ≤ 200 毫秒。
- 無障礙(accessibility / a11y)
- 讓視力不佳、不便操作的人也能順利使用網站的設計標準。WCAG AA 是國際標準的中間等級,要求文字與背景對比度 ≥ 4.5:1。
- 延遲載入(lazy loading)
- 畫面外的圖片先不下載,等使用者捲到附近才載,把頻寬留給第一眼內容。
- SEO(搜尋引擎優化)
- 讓網站在 Google 搜尋結果排得更前面的一整套方法,涵蓋技術體質、內容品質、連結結構三層。
常見問題FAQ
LCP 是什麼?
LCP = Largest Contentful Paint 中文「最大內容繪製」。Google Core Web Vitals 三大指標之一(LCP / INP / CLS)。指頁面「最大內容元素首次完成繪製」的時間點。Google 標準:< 2.5s 好 / 2.5-4s 中 / > 4s 差。是 page experience 的排名訊號之一(權重不大),同時也影響 UX。
怎麼知道頁面 LCP 元素是哪個?
去 pagespeed.web.dev 跑你的網址 拉到下方「深入分析」往下滑會有「最大內容繪製元素」區塊 直接告訴你哪個元素是 LCP 候選 + 它達到最終 pixel 的時間 + 元素的 selector。
加 JS 動畫一定會掉 perf 嗎?
不一定。動畫對 perf 的影響取決於 3 件事:1) 動畫元素是不是 LCP 候選(是的話會直接拖 LCP);2) 動畫用 opacity / transform 還是 width / height (前者 GPU 加速 後者 trigger layout);3) 動畫等多久才開始 (IO 等待會延遲)。Hero 區的動畫風險最大 文章內 cards 動畫風險小。
為什麼 IntersectionObserver 在 hero 沒用?
因為 IO 的用途是「等元素進 viewport 才執行 callback」 — 但 hero 一定在初始 viewport 內 IO 一進頁面就 fire 跟「不用 IO 直接執行」沒差。IO 該用在 scroll 下面的元素(下方 cards / 圖片 lazy load)才有 throttle 效果。
拿掉 typewriter 後 perf 真的回到 90+ 了嗎?
對 — TBT 從 100ms 降到 40ms、CLS 從 0.059 修到 0、LCP 回到 2.3s 範圍。但截至 2026-05-19 整體 perf 還有其他 culprit(Google Fonts 轉譯封鎖 1140ms / nes.min.css 阻擋 / forced reflow / critical chain 過深)Speed Index 還停在 4.4s。後續會在 SEO Journey #11 完整紀錄 perf 大整修。
我網站不在乎 SEO 還是要修 LCP 嗎?
如果你網站只靠社群流量不靠 Google 搜尋 那 LCP 對你影響小一些。但 LCP 也影響 UX — 使用者覺得「點進去要等才看到內容」就會跳走。所以即使不在乎 SEO 控制 LCP < 2.5s 對轉換率 / 跳出率還是有幫助。
看完這篇之前先確認:
- 加了 JS 動畫後 PageSpeed 掉分的人
- 想知道 LCP 是怎麼算出來的
- 做 build-in-public 想知道 perf 取捨
- 完全不在意 PageSpeed 的純內容站
- 已經 95+ 的站(這篇是新手 debug)
- 想找一鍵 perf 工具的人
