使用 Astro+Tailwind 實現 Dark mode

Astro
#Notes #Blog

紀錄如何使用 Astro+Tailwind 來實現 Dark mode 功能,並將狀態儲存在 localStorage,每次進入時檢查 localStorage 的 theme 來切換 mode

安裝 Tailwind

在 Astro 中有 astro add 指令來自動整合套件,可以直接在終端機輸入

npx astro add tailwind

接著他會問你是否繼續,以及幫你建立 tailwind.config.cjs 檔案,這邊就都 Yes 就可以了

結束後,你應該會看到根目錄幫你建立好 tailwind.config.cjs 檔案,以及在 astro.config.mjs 設定中的 integrations 裡看到多了一個 tailwind()

如果有問題也能參考一下官方文件

啟用 Dark mode

在剛剛安裝完後,你的 tailwind.config.cjs 大致上會長這樣

/** @type {import('tailwindcss').Config} */
module.exports = {
	content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
	theme: {
		extend: {},
	},
	plugins: [],
}

這時我們可以來啟用 Tailwind 的 Dark mode

只需要在 module 中多加一個 darkMode 就可以了

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
  darkMode: 'class', // or 'media' for system preference
  theme: {
    extend: {},
  },
  plugins: [],
}

darkMode 有兩種選項

  • class:透過 class 手動切換模式
  • media:當裝置設定是在深色模式時會自動切換模式

功能實作

這邊使用 React 元件實作,模式有 lightdark

一開始會先檢查用戶的主題偏好,如果是深色模式就直接回傳 dark

再來製作按鈕的 click function,如果當前 theme 是 light 則切換成 dark,反之,同時儲存到 localStorage 中

接著製作切換頁面模式的 function,如果在 light 模式就移除 root 的 dark class,否則新增 dark class

const themes = ['light', 'dark']

export default function ThemeToggle() {
  const getInitialTheme = () => {
    // 檢查用戶主題偏好
    if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
      return localStorage.getItem('theme');
    }
    if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
      return 'dark';
    }
    return 'light';
  };
  const [theme, setTheme] = useState(getInitialTheme());
  // Click Function
  const toggleTheme = () => {
    const newTheme = theme === 'light' ? 'dark' : 'light';
    localStorage.setItem('theme', newTheme);
    setTheme(newTheme);
  };

  useEffect(() => {
    applyTheme();
  }, [theme]);

  const applyTheme = () => {
    const root = document.documentElement;
    if (theme === 'light') {
      root.classList.remove('dark');
    } else {
      root.classList.add('dark');
    }
  };

  return (
    <div>
      {themes.map((t) => (
        <button
          key={t}
          type="button"
          onClick={toggleTheme}
          className={`p-2 rounded ${
            theme === t ? 'bg-gray-200 dark:bg-gray-800 text-black dark:text-white' : ''
          }`}
        >
          {t}
        </button>
      ))}
    </div>
  );
}

最後再把他匯入到畫面上就可以運作了

不過,有個小問題,可能會發現換頁時會有短暫閃爍,這跟元件渲染機制有關,因為當換頁時元件會觸發重新渲染,因此 dark mode 也就會重新新增一次

可以嘗試在畫面中加入這段 <script> 直接在客戶端去檢查 localStorage 加入 theme,避免元件重新渲染時造成的畫面閃爍問題

<script is:inline>
  if (typeof window !== "undefined") {
    const theme = localStorage.getItem('theme');
    if (theme === 'dark' || theme === 'light') {
      document.documentElement.classList.add(theme);
    }
  }
</script>

撰寫 style

上述功能如果都順利的話,應該可以發現 <html> 標籤上會多一個 class,能夠去切換 class="light"class="dark"

接著就可以來撰寫樣式了

寫法 dark: style

<div class="bg-white dark:bg-black">
  <p class="dark:text-white">這是一段文字</p>
</div>

參考來源