【Astro】修正深色模式閃爍 View Transitions

我在換頁時看到短暫的主題閃爍,按下 F5 重新整理時更明顯。這篇文章記錄修正方式與取捨。

問題 #

這個網站使用 Astro 的 view transitions 並有主題切換功能,換頁時,新文件可能在主題 class 重新套用前就先繪製。硬重新整理時,HTML 比 JavaScript 先到,瀏覽器會先畫出預設主題。

且一開始有使用 <ClientRouter /> 寫在 <head /> 也有在我的 Layout 組件上新增 astro:after-swap 監聽器(根據 Astro 官方文檔 描述的去做)

改動 #

嘗試把 <ClientRouter /> 寫在 src/layouts/BaseLayout.astro 加上 client router,並用一段 inline script 在交換前後同步主題:

import { ClientRouter } from "astro:transitions";

<!-- 省略 -->

<html lang="en">
  <head>
    <script is:inline>
      document.documentElement.style.visibility = "hidden";

      const applyTheme = () => {
        const isDark = getThemePreference() === "dark";
        document.documentElement.classList.toggle("dark", isDark);
        document.documentElement.style.colorScheme = isDark ? "dark" : "light";
        document.documentElement.style.visibility = "";
      };

      applyTheme();
      document.addEventListener("astro:before-swap", (event) => {
        const nextDocument = event.newDocument;
        const isDark = document.documentElement.classList.contains("dark");
        nextDocument.documentElement.classList.toggle("dark", isDark);
        nextDocument.documentElement.style.colorScheme = isDark ? "dark" : "light";
      });
      document.addEventListener("astro:after-swap", applyTheme);
    </script>
    <ClientRouter />
  </head>

  <!-- 省略 -->

</html>

某些固定 UI 組件可以加上 transition:persist 避免在轉場的移除又載入 UI(Maintain State 維持狀態)。

<Header
  client:load
  pathname={pathname}
  transition:persist
  transition:name="Header"
/>

備註 #

這樣可以降低硬重新整理的閃爍,但在純靜態站點無法完全保證首屏零閃。若要真正零閃,需要改成 SSR,並用 theme cookie 讓伺服器直接渲染正確的 class。