本文以
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
欢迎留言讨论其余方案。
🍺🍺🍺
参考文章