前言
更換主題的需求在移動端甚至一些有特殊需求的 pc 端都很常見了,大部分情況是由用戶點擊操作進行切換的
但如果,我們可以做到跟隨系統(tǒng)當前是 Light 還是 Dark 來自適應網(wǎng)頁的主題,看上去是不是很酷?
在這里,你可以 get 到
- 如何基于原生的
css variable來進行主題更換? - 怎么感知系統(tǒng)的主題切換并且觸發(fā)主題更換?
- 如何設計遠程動態(tài)主題加載方案,以適應用戶自定義主題配置?
想搶先看效果的看這里:https://ztstory.github.io/vue-theme/#/
思考與實踐
1、如何基于原生的 css 變量來進行主題更換?
說到前端的切換主題方案,有提到用less variable、sass variable、css variable等等,也都是比較成熟的方案了,我們主要聊一聊以 css variable為主的主題切換方案
利用變量的特性,我們可以比較容易的定義出我們的基礎主題樣式 Light
// 定義基礎色值,方便 UI 后期替換
:root {
--zt-c-white: #ffffff;
--zt-c-black: #181818;
--zt-c-primary: rgb(56, 173, 122);
--zt-c-text-light-1: #333;
--zt-c-text-light-2: #666;
--zt-c-text-dark-1: var(--zt-c-white);
--zt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
// Light 默認主題色
:root {
--color-background: var(--zt-c-white);
--color-heading: var(--zt-c-primary);
--color-text: var(--zt-c-text-light-1);
--color-text-2: var(--zt-c-text-light-2);
}
再者,我們要準備一套 Dark 主題色
:root {
--color-background: var(--zt-c-black);
--color-heading: var(--zt-c-primary);
--color-text: var(--zt-c-text-dark-1);
--color-text-2: var(--zt-c-text-dark-2);
}
準備好之后,問題來了:我們怎么切換這兩套變量呢?
目前來看是相互覆蓋的,所以我們要借助 css 的優(yōu)先級來進行區(qū)別命中
我們只需要在 body 標簽上增加一個自定義屬性:theme-mode="dark"
然后通過這個屬性值來匹配不同 tag 的主題色變量就完成基本的主題切換了
其他主題色切換同理
// 上面的代碼可以改造成這樣 .css
:root body[theme-mode="dark"] {
...
}
// .js
document.body.setAttribute("theme-mode", "dark");
為了視覺上絲滑一點,我們可以給 body 增加點過渡動畫,具體效果看上面的 demo 入口
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition: color 0.5s, background-color 0.5s;
}
2、如何感知系統(tǒng)的主題切換自動更換主題?
這里就要介紹一下 prefers-color-scheme 這個新的 css 特性,可能有很多小伙伴已經(jīng)知道了,但也可能有很多小伙伴不知道,這里簡單介紹一下。
prefers-color-scheme是 css 媒體特性 @media 中用于檢測用戶是否有將系統(tǒng)的主題色設置為亮色或者暗色。
| 語法 | 描述 |
|---|---|
| light | 表示用戶已告知系統(tǒng)他們選擇使用淺色主題的界面。 |
| dark | 表示用戶已告知系統(tǒng)他們選擇使用暗色主題的界面。 |
| no-preference | 表示系統(tǒng)未得知用戶在這方面的選項。在 boolean 值上下文中,執(zhí)行結(jié)果為 false |
若結(jié)果為
no-preference,無法通過此媒體特性獲知宿主系統(tǒng)是否支持設置主題色,或者用戶是否主動將其設置為無偏好。出于隱私保護等方面的考慮,用戶或用戶代理也可能在一些情況下在瀏覽器內(nèi)部將其設置為
no-preference。
借助這個特性我們可以將上述代碼改造一下
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--zt-c-black);
--color-heading: var(--zt-c-primary);
--color-text: var(--zt-c-text-dark-1);
--color-text-2: var(--zt-c-text-dark-2);
}
}
這樣就可以保證系統(tǒng)切換暗黑模式時,我們的頁面同樣也變?yōu)榘岛谀J嚼瞺
3. 如何設計遠程動態(tài)主題加載方案,以適應用戶自定義主題配置?
在做完上面的事情之后,我們基本上已經(jīng)可以做到既能手動切換主題,也能自動切換主題了
但是,我又萌生了一個想法,如果是用戶自定義主題上傳,然后使用的話,那我們該怎么設計這個系統(tǒng)?
首先,我們要滿足用戶自定義主題,就得先將主題設置的變量都定義好,讓用戶需要按照我們規(guī)定的一些變量來進行創(chuàng)作,同時也方便代碼維護
變量我們就以上面定義的背景色及文字顏色來表示,自定義主題也基本圍繞著這幾種變量來設計
其次,我們需要實現(xiàn)遠程動態(tài)加載主題的方案
我的思路是利用動態(tài)添加 <link rel='stylesheet' href='xxx' />的方式
這個方案需要注意避免兩個問題:
- 設置主題的過程中需保證主題資源 link 不會重復添加
- 不然真的會有很多很多個 link 標簽出現(xiàn),別問我怎么知道的
- 新舊主題資源切換時需注意控制主題過渡
- 開始沒注意,沒等新主題資源加載完成就刪除了舊主題,導致頁面瞬間變回默認的 Light 主題
最后,我們也要保證可以在遠程主題與本地主題的刷新保留問題
前面的工作做好,我們已經(jīng)可以實現(xiàn)各種姿勢的主題切換了(代碼可以看文末的 Demo)
但還有個問題沒解決,就是刷新之后主題又會回歸原樣。
我的想法是將 themeTag 持久化儲存,每次刷新時同步該主題,這樣基本上就保證了主題的一致性
但如果是遠程加載的主題的話,還是存在刷新后 link 標簽丟失,需要重新添加的情況,所以會存在一定的體驗問題,這個也希望各位看客們可以給一些意見共同討論一下怎么設計比較好~