本文以
react
爲例。
用css變量
來切換黑暗模式,易於維護和擴展。javascript
css變量
的用法:css
.selector { --black-color: #282c34; } :root { --black-color: #282c34; }
設置主題對應的CSS變量,切換主題只需切換css屬性的變量值。例如:切換APP元素的主題只需切換App的color
和background-color
對應CSS變量的變量值便可。前端
:root { /* 模式切換變量,默認light模式 */ --current-background-color: var(--light-background-color); --current-primary-color: var(--light-primary-color); /* 淺色主題 */ --light-primary-color: #666; --light-background-color: #fff; /* 深色主題 */ --dark-primary-color: #fff; --dark-background-color: #282c34; } .App { color: var(--current-primary-color); background-color: var(--current-background-color); min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); transition: background-color 0.3s; }
那麼如何切換:root
下--current-background-color
的值?java
1. 查找它 2. 替換它
// 找到:root下全部定義以--current變量 const currentCssVar = Array.from(document.styleSheets).reduce( (acc, sheet) => (acc = [ ...acc, ...Array.from(sheet.cssRules).reduce( (def, rule) => (def = rule.selectorText === ":root" ? [ ...def, ...Array.from(rule.style).filter((name) => name.startsWith("--current") ), ] : def), [] ), ]), [] );
currentCssVar.forEach((item) => { document.documentElement.style.setProperty( item, `var(--${themeName}${item.substring(9)})` ); });
完整JS代碼:react
import React, { useEffect, useState } from "react"; import Project from "@bit/toringo.comp.product-list"; import Switch from "@bit/campgladiator.cgui.components.atoms.switch"; import "./App.css"; import setTheme from "./util"; // 默認主題可來源與server、storage等。 const defaultTheme = 'light'; function App() { const [mode, setMode] = useState(defaultTheme); useEffect(() => { setTheme(mode); }, [mode]); return ( <div className="App"> <Switch onClick={() => setMode(mode === "dark" ? "light" : "dark")} /> <Project list={["Hulk", "Stack", "Link"]} /> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> </div> ); } export default App;
設置CSS變量,定義theme對應的CSS class選擇器,動態去改變className已達到主題切換。git
.App { /* color: var(--current-primary-color); background-color: var(--current-background-color); */ min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); transition: background-color 0.3s; } .App-light { color: var(--light-primary-color); background-color: var(--light-background-color); } .App-dark { color: var(--dark-primary-color); background-color: var(--dark-background-color); }
JS代碼:github
... <div className={`App ${mode === "dark" ? "App-dark" : "App-light"}`}> ....
利用css媒體查詢動態改變網頁主題樣式,當瀏覽器的主體發生變化時, 媒體查詢的prefers-color-scheme
會動態執行匹配的規則,瀏覽器
@media (prefers-color-scheme: dark) { :root { /* 淺色主題 */ --current-background-color: #282c34; --current-primary-color: #fff; } } @media (prefers-color-scheme: light) { :root { /* 深色主題 */ --current-background-color: #fff; --current-primary-color: #282c34; } } .App { color: var(--current-primary-color); background-color: var(--current-background-color); min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); transition: background-color 0.3s; }
利用JS事件監聽媒體查詢動態 改變網頁主題樣式,Web Animation API)還提供給了監聽css媒體查詢條件的匹配。oop
useEffect(() => { const mediaQuery = window.matchMedia("(prefers-color-scheme: light)"); const setFn = () => { setMode(mediaQuery.matches ? "light" : "dark"); }; mediaQuery.addEventListener("change", setFn); return () => { mediaQuery.removeListener("change", setFn); }; }, []);
以上線上Demo體驗flex
歡迎留言討論其餘方案。
🍺🍺🍺
參考文章