在 Astro Astrofy 樣板中實作 Theme Controller 功能

在 Astro Astrofy 樣板中實作 Theme Controller 功能


astro

本篇文章使用 Astro 的 Template 叫做 Astrofy ,這個樣板底下套用的 UI Framework 是 DaisyUI,所以文章內將會以此 UI 框架為基底,實作主題切換 (Theme Switch) 的功能。

首先我們要先看這個樣板是如何設定主題的,我們可以在 BaseLayout.astro 這個 Layout 當中找到 htmldata-theme 屬性

BaseLayout.astro
1
<html lang="en" data-theme="business"></html>

這個 data-theme 可以設定預設套用的 theme 名稱,DaisyUI 提供了很多樣化的主題可以讓使用者自行更改主題配色,以下列出 DaisyUI 支援的主題配色清單

tailwind.config.js
1
module.exports = {
2
//...
3
daisyui: {
4
themes: [
5
"light",
6
"dark",
7
"cupcake",
8
"bumblebee",
9
"emerald",
10
"corporate",
11
"synthwave",
12
"retro",
13
"cyberpunk",
14
"valentine",
15
"halloween",
16
"garden",
17
"forest",
18
"aqua",
19
"lofi",
20
"pastel",
21
"fantasy",
22
"wireframe",
23
"black",
24
"luxury",
25
"dracula",
26
"cmyk",
27
"autumn",
28
"business",
29
"acid",
30
"lemonade",
31
"night",
32
"coffee",
33
"winter",
34
"dim",
35
"nord",
36
"sunset",
37
],
38
},
39
}

接下來我們就要在畫面中加上 Theme Controller component,讓使用者有地方可以切換主題,我這邊選擇的是加入到 sidebar footer ,當然,也可以加在其他適合的區塊當中

SideBarFooter.astro
1
<label class="swap swap-rotate mx-3">
2
<input type="checkbox" id="theme-controller" class="theme-controller" value="cupcake"/>
3
<svg class="swap-off fill-current w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
4
<path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z"/>
5
</svg>
6
<svg class="swap-on fill-current w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
7
<path d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z"/>
8
</svg>
9
</label>

這邊我把 Theme Controller 的 input 加上 id="theme-controller 方便寫 javascript 可以抓到,而 input 上面的 value 代表的是當這個 checkbox 被勾選的時候會套用的主題名稱

所以在這個架構底下,可以稍微先整理一下如果今天要用 javascript 實作具有記憶功能的 Theme Switch 功能,我們先簡單列出預期的功能

  1. 讀取目前瀏覽器設定值 (prefers-color-scheme),如果是 dark 就套用顏色深的主題,反之,則套用淺色的主題
  2. Theme Controller 在切換主題時,要能夠將目前的主題名稱儲存到 localStorage 當中
  3. 切換頁面、刷新頁面時,主題不會跑掉 (路由切換時不會跑掉)
BaseLayout.astro
1
<script is:inline>
2
(function () {
3
const themes = {dark: "business", light: "cupcake"};
4
// 取得目前瀏覽器的設定
5
const sysColor = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
6
7
// astro:page-load 在每次路由變更的時候都會觸發,這就是在這裡沒有用 DOMContentLoaded 的原因
8
// 說明: 如果 user 改變路由,astro 不會刷新 (這行為跟 SPA 是一樣的)
9
document.addEventListener("astro:page-load", () => {
10
const controller = document.getElementById("theme-controller");
11
// 第一次沒有設定存在 local storage 裡面時,先用瀏覽器設定寫入到 local storage 當中
12
if (localStorage.getItem("theme") === null) localStorage.setItem("theme", sysColor);
13
document.querySelector("html").setAttribute("data-theme", themes[localStorage.getItem("theme")]);
14
15
// 同步 theme controller 的外觀,如果沒有這樣設定的話,theme controller 在切換成 light 主題並且路由變更後會是呈現太陽符號
16
if (localStorage.getItem("theme") === "light") controller.click();
17
18
// 監聽 theme controller 異動 (true = 亮色主題,false = 暗色主題),並且把設定寫到 local storage (記憶功能)
19
// 最後再把他設定到 `<html lang="en" data-theme="..."></html>` 上面
20
controller.addEventListener("change", (e) => {
21
localStorage.setItem("theme", e.target.checked ? "light" : "dark");
22
document.querySelector("html").setAttribute("data-theme", themes[localStorage.getItem("theme")]);
23
});
24
});
25
})();
26
</script>

以上就是如何在 Astro Astrofy Template 當中實作具有記憶功能的 Theme Controller

參考資料

© 2024 Ryan | 不務正業 全端開發記事