React 主题切换(方案分享🤩)

前段时间做了个小的个人起始页,一直在想加什么功能进去,也有添加主题切换的想法,但是由于该页面有点单一加上我并没有去调研过主题切换的方案,因此一直搁置。

这个项目的初衷是除了满足我个人使用浏览器的需求外,也想多学习一些前端知识,故而最近抽出时间调研了一下现有方案并在这个项目中实现一下。

在调研的方案中,我选取了如下四个方案:

  1. React Context
  2. CSS变量 + className切换
  3. 使用CSS变量 + CSSStyleDeclaration.setProperty()
  4. 特殊的canvas方案(展示)

其中方案3和方案4将主要应用在-->个人起始页实现,方案1和方案2作为小Demo在这里-->Demo

React Context

使用React Context可以有两种方式来实现主题切换(前提是要先会使用 Context 深层传递参数):

  1. 仅指定主题(即当前使用主题的名称),主题的具体样式在对应需要切换主题的组件中进行编写。
  2. 将主题的主要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方案(展示)

由于内容已经很多了,为此我这里先给大家看看效果,下一篇会描述如何实现👻。

相关推荐
庸俗今天不摸鱼29 分钟前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
QTX1873030 分钟前
JavaScript 中的原型链与继承
开发语言·javascript·原型模式
黄毛火烧雪下36 分钟前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox1 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞1 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行1 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_593758101 小时前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox
掘金一周1 小时前
金石焕新程 >> 瓜分万元现金大奖征文活动即将回归 | 掘金一周 4.3
前端·人工智能·后端
三翼鸟数字化技术团队1 小时前
Vue自定义指令最佳实践教程
前端·vue.js
Jasmin Tin Wei2 小时前
蓝桥杯 web 学海无涯(axios、ecahrts)版本二
前端·蓝桥杯