扎根AI助理 種下 2026-07-14 · 最後照顧 2026-07-14⇄ 在圖譜中查看

深色模式的防閃爍:一段必須跑在最前面的腳本

深色模式的難點不在 CSS——變數換值而已——在於 SSR 的第一幀。伺服器不知道訪客偏好哪個主題,吐出的 HTML 只能預設淺色;等 JS 掛載後讀 localStorage 再切換,深色使用者就會看到「白屏一閃、再轉黑」。這一閃在深夜的螢幕上刺眼得像個 bug。

解法:在首次繪製前釘住主題

一段幾行的 inline script,放在 <head>任何樣式表和框架之前執行:讀 localStorage 的使用者選擇(沒有就問系統偏好 prefers-color-scheme),把 data-theme 直接寫在 <html> 上。瀏覽器開始繪製時主題已經定案,一幀都不會錯。

html
<script>
  (function () {
    var t = localStorage.getItem('theme');
    if (t !== 'dark' && t !== 'light')
      t = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
    document.documentElement.setAttribute('data-theme', t);
  })();
</script>

整站的顏色全走 CSS 變數(單一 tokens 檔),兩個主題就是兩組變數值——data-theme 一釘,所有顏色瞬間就位。

Hydration 的第二個坑

Vue 的狀態在 SSR 時不知道主題,初值只能設淺色;如果 UI 上有依主題渲染的文字(切換鈕的「亮/暗」標籤),server 版與 client 版會對不上——hydration mismatch。解法分兩層:顏色類的呈現全部交給 CSS 選擇器(跟著 data-theme 走,不經過 JS 狀態);文字類的包 <ClientOnly> 延後渲染。主題切換鈕的日月圖形因此是純 CSS 的兩個相位——狀態在 DOM 屬性上,不在元件裡。

原則濃縮成一句:主題是文件層級的狀態,不是元件狀態。放對層級,閃爍與 mismatch 都不藥而癒。

#前端#nuxt

讀到這裡若有一點收穫,替這顆種子澆一次水——匿名、一人一次。