前段时间做了个小的个人起始页,一直在想加什么功能进去,也有添加主题切换的想法,但是由于该页面有点单一加上我并没有去调研过主题切换的方案,因此一直搁置。
这个项目的初衷是除了满足我个人使用浏览器的需求外,也想多学习一些前端知识,故而最近抽出时间调研了一下现有方案并在这个项目中实现一下。
在调研的方案中,我选取了如下四个方案:
- React Context
- CSS变量 + className切换
- 使用CSS变量 + CSSStyleDeclaration.setProperty()
- 特殊的canvas方案(展示)
其中方案3和方案4将主要应用在-->个人起始页实现,方案1和方案2作为小Demo在这里-->Demo。
React Context
使用React Context可以有两种方式来实现主题切换(前提是要先会使用 Context 深层传递参数):
- 仅指定主题(即当前使用主题的名称),主题的具体样式在对应需要切换主题的组件中进行编写。
- 将主题的主要CSS,以CSSProperties(样式对象)的形式存入Context,在特定组件中使用Context变量。
实际上以上两种方式异曲同工,在此以 ① 举例。 首先在一个新的文件中创建Context并导出:
js
import { createContext } from 'react';
//指定默认值
const defaultTheme = 'light';
//创建context
const ThemeContext = createContext(defaultTheme);
//导出
export default ThemeContext;
其次就是使用了,在项目的某个根组件处使用我们创建的ThemeContext.Provider
,并设置状态:
js
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<Demo />
<Button
onClick={() => {
setTheme(theme === 'light' ? 'dark' : 'light');
}}
>
主题切换
</Button>
</ThemeContext.Provider>
);
}
接下来我们只需要在需要切换样式的组件上获取Context中的值,并绑定相应的样式即可:
css
.box{
width: 200px;
height: 200px;
}
.light{
background-color: #fff;
color: #333;
}
.dark{
background-color: #333;
color: #fff;
}
js
import ThemeContext from './ThemeContext';
import './style.css';
export default function Demo() {
const theme = useContext(ThemeContext);
return <div className={`box ${theme}`}>Context Demo</div>;
}
如果你想像②中所述的以CSSProperties(样式对象)的形式存入Context,为了简化Context的使用,可以像这样封装:
js
import { createContext, useContext, useMemo, useState } from "react";
const THEMES = {
light: {
text: '#145f32',
cardBGColor: "#ffb002",
},
dark: {
text: '#ffffff',
cardBGColor: "#145f32",
}
}
const themeValue = {
theme: THEMES.light,
toggleTheme: () => null,
mode: "light",
}
const ThemeContext = createContext(themeValue)
export const useThemeContext = () => useContext(ThemeContext)
export const ThemeProvider = (props) => {
const [mode, setMode] = useState("light")
const theme = useMemo(() => {
return mode === "light" ? THEMES.light : THEMES.dark
}, [mode])
const toggleTheme = () => {
setMode(mode === "light" ? "dark" : "light")
}
return <ThemeContext.Provider {...props} value={{theme, toggleTheme, mode}}/>
}
CSS变量 + className
首先我们需要在根样式文件中定义好CSS变量以及对应主题类的变量取值:
css
:root {
/*默认白色主题取值*/
--theme-color: #333;
--theme-backgroundColor: #fff;
}
/*黑色主题对应的变量取值*/
.dark{
--theme-color: #fff;
--theme-backgroundColor: #333;
}
接下来在我们在需要切换主题的组件中使用CSS变量来指定样式:
css
.box{
width: 200px;
height: 200px;
background-color: var(--theme-backgroundColor);
color: var(--theme-color);
}
最后我们只需要应用即可,这里的方案是将.dark
类名在<html>
标签中添加/删除。
js
<button
onClick={() => {
const classList = document.documentElement.classList;
if (classList.value) {
document.documentElement.classList.remove('dark');
return;
}
document.documentElement.classList.add('dark');
}}
>
主题切换
</button>;
使用CSS变量 + CSSStyleDeclaration.setProperty()
首先我们需要学习一下 CSSStyleDeclaration.setProperty()
看一下MDN的描述,结合我们本节标题,可能你已经知道该怎么做了🤩。
此时我们需要在组件样式上使用变量来指定样式,之后只需要调用如下函数即可:
js
export const setCssVar = (cssVar, value) => {
document.documentElement.style.setProperty(cssVar, value);
};
此处将对应的CSS变量和值就会直接挂载到<html>
标签上。
看到这里,你会发现,其实还有些方案是可以根据上述3点部分结合的,大家选择合适的就行!
特殊的canvas方案(展示)
由于内容已经很多了,为此我这里先给大家看看效果,下一篇会描述如何实现👻。