Web前端主題切換方案

前端主題切換方案

Web前端主題切換方案

現在我們經常可以看到一些網站會有類似暗黑模式/白天模式的主題切換功能,效果也是十分炫酷,在平時的開發場景中也有越來越多這樣的需求,這裡大致羅列一些常見的主題切換方案並分析其優劣,大家可根據需求綜合分析得出一套適用的方案。

方案1:link標籤動態引入

其做法就是提前準備好幾套CSS主題樣式檔案,在需要的時候,建立link標籤動態載入到head標籤中,或者是動態改變link標籤的href屬性。

Web前端主題切換方案

表現效果如下:

Web前端主題切換方案

網路請求如下:

Web前端主題切換方案

優點:

實現了按需載入,提高了首屏載入時的效能

缺點:

動態載入樣式檔案,如果檔案過大網路情況不佳的情況下可能會有載入延遲,導致樣式切換不流暢

如果主題樣式表內定義不當,會有優先順序問題

各個主題樣式是寫死的,後續針對某一主題樣式表修改或者新增主題也很麻煩

方案2:提前引入所有主題樣式,做類名切換

這種方案與第一種比較類似,為了解決反覆載入樣式檔案問題提前將樣式全部引入,在需要切換主題的時候將指定的根元素類名更換,相當於直接做了樣式覆蓋,在該類名下的各個樣式就統一地更換了。其基本方法如下:

/* day樣式主題 */body。day 。box { color: #f90; background: #fff;}/* dark樣式主題 */body。dark 。box { color: #eee; background: #333;}。box { width: 100px; height: 100px; border: 1px solid #000;}

hello

選擇樣式:

function change(theme) { document。body。className = theme;}

表現效果如下:

Web前端主題切換方案

優點:

不用重新載入樣式檔案,在樣式切換時不會有卡頓

缺點:

首屏載入時會犧牲一些時間載入樣式資源

如果主題樣式表內定義不當,也會有優先順序問題

各個主題樣式是寫死的,後續針對某一主題樣式表修改或者新增主題也很麻煩

方案小結

透過以上兩個方案,我們可以看到對於樣式的載入問題上的考量就類似於在糾結是做SPA單頁應用還是MPA多頁應用專案一樣。兩種其實都誤傷大雅,但是最重要的是要保證在後續的持續開發迭代中怎樣會更方便。因此我們還可以基於以上存在的問題和方案做進一步的增強。

在做主題切換技術調研時,看到了網友的一條建議:

Web前端主題切換方案

因此下面的幾個方案主要是針對變數來做樣式切換

方案3:CSS變數+類名切換

靈感參考:Vue3官網

在Vue3官網有一個暗黑模式切換按鈕,點選之後就會平滑地過渡,雖然Vue3中也有一個v-bind特性可以實現動態樣式繫結,但經過觀察以後Vue官網並沒有採取這個方案,針對Vue3的v-bind特性在接下來的方案中會細說。

大體思路跟方案2相似,依然是提前將樣式檔案載入,切換時將指定的根元素類名更換。不過這裡相對靈活的是,預設在根作用域下定義好CSS變數,只需要在不同的主題下更改CSS變數對應的取值即可。

順帶提一下,在Vue3官網還使用了color-scheme: dark;將系統的捲軸設定為了黑色模式,使樣式更加統一。

html。dark { color-scheme: dark;}

實現方案如下:

/* 定義根作用域下的變數 */:root { ——theme-color: #333; ——theme-background: #eee;}/* 更改dark類名下變數的取值 */。dark{ ——theme-color: #eee; ——theme-background: #333;}/* 更改pink類名下變數的取值 */。pink{ ——theme-color: #fff; ——theme-background: pink;}。box { transition: all 。2s; width: 100px; height: 100px; border: 1px solid #000; /* 使用變數 */ color: var(——theme-color); background: var(——theme-background);}

表現效果如下:

Web前端主題切換方案

優點:

不用重新載入樣式檔案,在樣式切換時不會有卡頓

在需要切換主題的地方利用var()繫結變數即可,不存在優先順序問題

新增或修改主題方便靈活,僅需新增或修改CSS變數即可,在var()繫結樣式變數的地方就會自動更換

缺點:

IE相容性(忽略不計)

首屏載入時會犧牲一些時間載入樣式資源

方案4:Vue3新特性(v-bind)

雖然這種方式存在侷限性只能在Vue開發中使用,但是為Vue專案開發者做動態樣式更改提供了又一個不錯的方案。

簡單用法

Vue3中在style樣式透過v-bind()繫結變數的原理其實就是給元素繫結CSS變數,在繫結的資料更新時呼叫CSSStyleDeclaration。setProperty更新CSS變數值。

實現思考

前面方案3基於CSS變數繫結樣式是在:root上定義變數,然後在各個地方都可以獲取到根元素上定義的變數。現在的方案我們需要考慮的問題是,如果是基於JS層面如何在各個元件上優雅地使用統一的樣式變數?

我們可以利用Vuex或Pinia對全域性樣式變數做統一管理,如果不想使用類似的外掛也可以自行封裝一個hook,大致如下:

// 定義暗黑主題變數export default { fontSize: ‘16px’, fontColor: ‘#eee’, background: ‘#333’,};

// 定義白天主題變數export default { fontSize: ‘20px’, fontColor: ‘#f90’, background: ‘#eee’,};

import { shallowRef } from ‘vue’;// 引入主題import theme_day from ‘。/theme_day’;import theme_dark from ‘。/theme_dark’;// 定義在全域性的樣式變數const theme = shallowRef({});export function useTheme() { // 嘗試從本地讀取 const localTheme = localStorage。getItem(‘theme’); theme。value = localTheme ? JSON。parse(localTheme) : theme_day; const setDayTheme = () => { theme。value = theme_day; }; const setDarkTheme = () => { theme。value = theme_dark; }; return { theme, setDayTheme, setDarkTheme, };}

使用自己封裝的主題hook

表現效果如下:

Web前端主題切換方案

其實從這裡可以看到,跟Vue的響應式原理一樣,只要資料發生改變,Vue就會把綁定了變數的地方通通更新。

優點:

不用重新載入樣式檔案,在樣式切換時不會有卡頓

在需要切換主題的地方利用v-bind繫結變數即可,不存在優先順序問題

新增或修改主題方便靈活,僅需新增或修改JS變數即可,在v-bind()繫結樣式變數的地方就會自動更換

缺點:

IE相容性(忽略不計)

首屏載入時會犧牲一些時間載入樣式資源

這種方式只要是在元件上綁定了動態樣式的地方都會有對應的編譯成雜湊化的CSS變數,而不像方案3統一地就在:root上設定(不確定在達到一定量級以後的效能),也可能正是如此,Vue官方也並未採用此方式做全站的主題切換

方案5:SCSS + mixin + 類名切換

主要是運用SCSS的混合+CSS類名切換,其原理主要是將使用到mixin混合的地方編譯為固定的CSS以後,再透過類名切換去做樣式的覆蓋,實現方案如下:

定義SCSS變數

/* 字型定義規範 */$font_samll:12Px;$font_medium_s:14Px;$font_medium:16Px;$font_large:18Px;/* 背景顏色規範(主要) */$background-color-theme: #d43c33;//背景主題顏色預設(網易紅)$background-color-theme1: #42b983;//背景主題顏色1(QQ綠)$background-color-theme2: #333;//背景主題顏色2(夜間模式)/* 背景顏色規範(次要) */ $background-color-sub-theme: #f5f5f5;//背景主題顏色預設(網易紅)$background-color-sub-theme1: #f5f5f5;//背景主題顏色1(QQ綠)$background-color-sub-theme2: #444;//背景主題顏色2(夜間模式)/* 字型顏色規範(預設) */$font-color-theme : #666;//字型主題顏色預設(網易)$font-color-theme1 : #666;//字型主題顏色1(QQ)$font-color-theme2 : #ddd;//字型主題顏色2(夜間模式)/* 字型顏色規範(啟用) */$font-active-color-theme : #d43c33;//字型主題顏色預設(網易紅)$font-active-color-theme1 : #42b983;//字型主題顏色1(QQ綠)$font-active-color-theme2 : #ffcc33;//字型主題顏色2(夜間模式)/* 邊框顏色 */$border-color-theme : #d43c33;//邊框主題顏色預設(網易)$border-color-theme1 : #42b983;//邊框主題顏色1(QQ)$border-color-theme2 : #ffcc33;//邊框主題顏色2(夜間模式)/* 字型圖示顏色 */$icon-color-theme : #ffffff;//邊框主題顏色預設(網易)$icon-color-theme1 : #ffffff;//邊框主題顏色1(QQ)$icon-color-theme2 : #ffcc2f;//邊框主題顏色2(夜間模式)$icon-theme : #d43c33;//邊框主題顏色預設(網易)$icon-theme1 : #42b983;//邊框主題顏色1(QQ)$icon-theme2 : #ffcc2f;//邊框主題顏色2(夜間模式)

定義混合mixin

@import “。/variable。scss”;@mixin bg_color(){ background: $background-color-theme; [data-theme=theme1] & { background: $background-color-theme1; } [data-theme=theme2] & { background: $background-color-theme2; }}@mixin bg_sub_color(){ background: $background-color-sub-theme; [data-theme=theme1] & { background: $background-color-sub-theme1; } [data-theme=theme2] & { background: $background-color-sub-theme2; }}@mixin font_color(){ color: $font-color-theme; [data-theme=theme1] & { color: $font-color-theme1; } [data-theme=theme2] & { color: $font-color-theme2; }}@mixin font_active_color(){ color: $font-active-color-theme; [data-theme=theme1] & { color: $font-active-color-theme1; } [data-theme=theme2] & { color: $font-active-color-theme2; }}@mixin icon_color(){ color: $icon-color-theme; [data-theme=theme1] & { color: $icon-color-theme1; } [data-theme=theme2] & { color: $icon-color-theme2; }}@mixin border_color(){ border-color: $border-color-theme; [data-theme=theme1] & { border-color: $border-color-theme1; } [data-theme=theme2] & { border-color: $border-color-theme2; }}

表現效果如下:

Web前端主題切換方案

可以發現,使用mixin混合在SCSS編譯後同樣也是將所有包含的樣式全部載入:

Web前端主題切換方案

這種方案最後得到的結果與方案2類似,只是在定義主題時由於是直接操作的SCSS變數,會更加靈活。

優點:

不用重新載入樣式檔案,在樣式切換時不會有卡頓

在需要切換主題的地方利用mixin混合繫結變數即可,不存在優先順序問題

新增或修改主題方便靈活,僅需新增或修改SCSS變數即可,經過編譯後會將所有主題全部編譯出來

缺點:

首屏載入時會犧牲一些時間載入樣式資源

方案6:CSS變數+動態setProperty

此方案較於前幾種會更加靈活,不過視情況而定,這個方案適用於由使用者根據顏色面板自行設定各種顏色主題,這種是主題顏色不確定的情況,而前幾種方案更適用於定義預設的幾種主題。

方案參考:vue-element-plus-admin

主要實現思路如下:

只需在全域性中設定好預設的全域性CSS變數樣式,無需單獨為每一個主題類名下重新設定CSS變數值,因為主題是由使用者

動態

決定。

:root { ——theme-color: #333; ——theme-background: #eee;}

定義一個工具類方法,用於修改指定的CSS變數值,呼叫的是CSSStyleDeclaration。setProperty

export const setCssVar = (prop: string, val: any, dom = document。documentElement) => { dom。style。setProperty(prop, val)}

在樣式發生改變時呼叫此方法即可

setCssVar(‘——theme-color’, color)

表現效果如下:

Web前端主題切換方案

vue-element-plus-admin主題切換原始碼:

Web前端主題切換方案

這裡還用了vueuse的useCssVar不過效果和Vue3中使用v-bind繫結動態樣式是差不多的,底層都是呼叫的CSSStyleDeclaration。setProperty這個api,這裡就不多贅述vueuse中的用法。

Web前端主題切換方案

優點:

不用重新載入樣式檔案,在樣式切換時不會有卡頓

仔細琢磨可以發現其原理跟方案4利用Vue3的新特性v-bind是一致的,只不過此方案只在:root上動態更改CSS變數而Vue3中會將CSS變數繫結到任何依賴該變數的節點上。

需要切換主題的地方只用在:root上動態更改CSS變數值即可,不存在優先順序問題

新增或修改主題方便靈活

缺點:

IE相容性(忽略不計)

首屏載入時會犧牲一些時間載入樣式資源(相對於前幾種預設好的主題,這種方式的樣式定義在首屏載入基本可以忽略不計)

方案總結

說明:兩種主題方案都支援並不代表一定是最佳方案,視具體情況而定。

方案/主題樣式

固定預設主題樣式

主題樣式不固定

方案1:link標籤動態引入

√(檔案過大,切換延時,不推薦)

×

方案2:提前引入所有主題樣式,做類名切換

×

方案3:CSS變數+類名切換

√(推薦)

×

方案4:Vue3新特性(v-bind)

√(效能不確定)

√(效能不確定)

方案5:SCSS + mixin + 類名切換

√(推薦,最終呈現效果與方案2類似,但定義和使用更加靈活)

×

方案6:CSS變數+動態setProperty

√(更推薦方案3)

√(推薦)