Web前端主題切換的幾種方案

Dickens發表於2021-12-29

歡迎關注我的公眾號睿Talk,獲取我最新的文章:
clipboard.png

一、前言

本文將介紹 Web 前端主題切換的幾種常用方案,示例程式碼基於 React 框架。廢話少說,show you the code!

二、場景一:預定義主題

這種場景比較常見的情況是預定義淺色和深色 2 個主題,可以有 2 種實現方案。

方案1:CSS 屬性覆蓋

這種方案利用了css多層樣式精確匹配的特點,通過樣式覆蓋的方式實現主題的切換。
首先需要在應用的根元素中設一個 class,切換主題時給 class 賦上對應的值,下面以theme1/theme2為例。

h2 {
  color: brown;
}
button {
  background-color: yellow;
}
.theme2 h2 {
  color: blue;
}
.theme2 button {
  background-color: green;
}
import './combine.css';
export default function App() {
  const [theme, setTheme] = useState('theme1');
  return (
    // 切換應用根元素的 class
    <div className={theme}>
      <h2>{theme}</h2>
      <button onClick={() => {
        setTheme(theme => { if (theme === 'theme1') return 'theme2'; return 'theme1' })
      }}>切換主題</button>
    </div>
  );
}

方案2:css變數替換

這個方案跟方案一的思路是相似的,區別是使用了 css變數 來定義主題色。
首先要在頁面的根元素上定義一個 dataset: <html lang="en" data-theme="theme1">
然後定義不同主題的變數值:

<style id="theme-var">
  :root {
        --font-color: brown;
        --button-color: yellow;
      }
  :root[data-theme=theme2] {
    --font-color: blue;
    --button-color: green;
  } 
</style>
h2 {
  color: var(--font-color, black)
}
button {
  background-color: var(--button-color, gray);
}
import './var.css';
export default function App() {
  const [theme, setTheme] = useState('theme1');
  return (
    <div>
      <h2>{theme}</h2>
      <button onClick={() => {
        const doc = document.documentElement;
        const newTheme = theme === 'theme1' ? 'theme2' : 'theme1';
        // 設定頁面根元素的 dataset
        doc.dataset.theme = newTheme;
        setTheme(newTheme);
      }}>切換主題</button>
    </div>
  );
}

三、場景二:允許使用者自定義主題

這種場景與場景一的最大區別是無法預定義 CSS 屬性和變數,一般是要搭配介面來實現動態替換的功能。下面的例子只演示原理,忽略介面資料的處理。

方案1:全量替換CSS

這個方案比較簡單粗暴,需要將頁面的所有 css 打包在一起,放在預定義好 ID 的 style 標籤中,切換主題的時候將 css 全量替換掉。

// 假設下面的 css 是從介面獲取的字串
const themeAll1 = 'h2 {color: brown;} button {background-color: yellow;}'
const themeAll2 = 'h2 {color: blue;} button {background-color: green;}'
export default function App() {
  const [theme, setTheme] = useState('主題1');
  return (
    <div>
      <h2>{theme}</h2>
      <button onClick={() => {
        // 替換 style 標籤的內容
        if (theme === '主題1') {
          document.getElementById('theme-all').innerText = themeAll2;
          setTheme('主題2')
        } else {
          document.getElementById('theme-all').innerText = themeAll1;
          setTheme('主題1')
        }

      }}>切換主題</button>
    </div>
  );
}

方案2:CSS變數替換

方案一比較簡單粗暴,資料量也比較大。可以使用 css變數 來進行優化,抽取主題色變數,放在根偽類下面。切換主題時只需要動態設定 style 標籤內 css 變數的值。

<style id="theme-var">
  :root {
    --font-color: brown;
    --button-color: yellow;
  }
</style>
h2 {
  color: var(--font-color, black)
}
button {
  background-color: var(--button-color, gray);
}
import './var.css';
// 假設下面的 css 是從介面獲取的字串
const themeVar1 = ':root {--font-color: brown; --button-color: yellow;}'
const themeVar2 = ':root {--font-color: blue; --button-color: green;}'
export default function App() {
  const [theme, setTheme] = useState('主題1');
  return (
    <div>
      <h2>{theme}</h2>
      <button onClick={() => {
        // 替換 style 標籤的內容
        if (theme === '主題1') {
          document.getElementById('theme-var').innerText = themeVar2;
          setTheme('主題2')
        } else {
          document.getElementById('theme-var').innerText = themeVar1;
          setTheme('主題1')
        }
      }}>切換主題</button>
    </div>
  );
}

六、總結

本文介紹了 4 種常用的主題切換方案,當中最後一個方案最靈活,可以配合 API 擴充套件無限量的主題。對於更常見的淺色加深色主題模式,可以選擇第 2 種方案。他們的共同點都是使用了 css 變數抽取主題顏色,實現起來非常優雅。

相關文章