2023/05/26
隨著深色模式(Dark Mode)在現代網頁中越來越受歡迎,在網頁中提供Dark Mode切換功能好像是一種越來越常見的設計趨勢。
而Next.js是非常受歡迎的的React網頁框架,在最新版Next.js 13提供appDir作為beta版的功能,其特色是打造更少客戶端(Client)的JS程式碼,建構可重用的Layouts,以目錄作為Routes切換…等等。
由於beta版本的實驗性質,目前在next dev開發網頁深色模式功能時,經常會出現Hydration過程相關的錯誤, 本文將用簡化的例子講解我是如何在Next.js app開發自定義顏色的深色模式按鈕(Dark Mode Switch / Theme Toggle),讓使用者在網頁點擊切換(switch)淺色模式(Light Mode)及深色模式(Dark Mode),在開發時遇到的問題及目前的解決方式。
接下來的文章假設已經設置好Tailwind CSS在Next.js專案中開發。
Tailwind CSS作為CSS框架,依照官方文件說明若要手動切換深色模式,而非依照裝置系統主題顏色偏好,需要使用class strategy而不是media strategy,將tailwind.config.js
的darkMode
值修改成:
之後在Html的元素中只要有dark:
class值出現,表示將會應用到Dark Mode樣式
ex:
next-themes
是一個能幫助我們簡化繁瑣設定程序的Next.js套件,提供較為簡易的API來切換Next.js App的theme模式。官方文件在這裡: https://github.com/pacocoursey/next-themes
它的文件寫的相當仔細,也包含Next appDir 最新的應用方式,也比Tailwind CSS文件涵蓋的範圍還要廣,若要自定義主題建議以該文件作為參考主軸。
將Providers
加入layout.tsx
:
注意
如果不將 suppressHydrationWarning 加到<html>
中,網頁控制台將收到警告,因為next-themes
會更新該元素。此屬性僅應用一層深度,因此不會阻擋其他元素上的Hydration警告。
搞懂前面的設定就已經花費不少時間,讓我們直接使用現成的UI按鈕來點擊切換色彩模式吧!
我使用的是UI套件是daisyUI,它是免費、開源的Tailwind CSS 插件(plugin),優點是輕量、設定簡單、UI樣式現代簡約。
官方文件: https://daisyui.com/docs/install/
tailwind.config.js
中的插件並加上選擇的主題,例如選擇Daisy UI 提供的 garden 主題:這樣的設定表示Tailwind預設的light theme將應用garden theme組件樣式,而dark theme我們將維持原先Tailwind在元素中樣式名稱prefix寫法(dark:
)。
更多的主題設定,參考: https://daisyui.com/docs/themes/
為了接下來使用daisyUI Toggle
組件能夠正常顯示,需要至少選定一個主題,或是將主題設置成false
,表示應用預設light theme
組件樣式:
在專案中建立DarkModeSwitch.tsx
組件,我們讓它在網頁中顯示於畫面右上方固定位置。
使用daisyUI Toggle https://daisyui.com/components/toggle/
由於我們無法在伺服器端取得theme的值,所以從useTheme
返回的許多值在mounted到客戶端之前都是未定義的。
如果在mounting到客戶端之前嘗試根據當前theme渲染 UI,我們將看到Hydration不匹配(mismatch UI)錯誤警告視窗:
Fix
我們需要使用 useEffect + setMounted useState 確保組件在客戶端加載:
避免網頁彈出Error: Hydration警告視窗。
相關問題討論:
我們透過{theme}
變量可以觀察當前系統所應用的主題,分別是system, light, dark,使用者第一次開啟網頁時瀏覽器會先套用system樣式,但是根據我們的toggleTheme
回呼函數,我們的主題切換是依據{theme}
的值是light或dark來判斷,因此第一次點擊深色模式按鈕時,網頁畫面主題可能不會改變。為了避免混淆,我們設定忽略系統主題顏色,並讓使用者第一次開啟網頁時瀏覽預設的Light Theme。在ThemeProvider設定屬性enableSystem={false}
,使{theme}
值預設為light,使{theme}
值在light、dark兩個值相互切換: