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方案(展示)

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

相关推荐
随风一样自由8 小时前
【前端独角兽】刚进入前端领域的前端开发用jQuery还是Vue3?
前端·javascript·vue3·jquery
IMPYLH8 小时前
HTML 的 <data> 元素
前端·html
YHHLAI9 小时前
[特殊字符] 面试中的 Promise —— 从入门到精通
前端·javascript·面试
weixin_BYSJ19879 小时前
SpringBoot + MySQL 乒乓球运动员信息管理系统项目实战--附源码04954
java·javascript·spring boot·python·django·flask·php
沪飘大军10 小时前
ll-llm:一个零依赖、可插拔的 OpenAI 兼容 LLM 代理
javascript
kyriewen11 小时前
我扒了 GPT-5.6 的全部编程跑分——有一项 OpenAI 没敢放出来
前端·gpt·ai编程
前端H11 小时前
微前端 v3 架构升级:从加载隔离到状态协同
前端·架构·状态模式
山河木马12 小时前
GPU自动处理专题3-NDC怎么映射成屏幕像素坐标(视口变换)
javascript·webgl·图形学
陆枫Larry12 小时前
小程序包体积优化:用 SVG 图片替换 iconfont,并保留 CSS 控色能力
前端
Appthing13 小时前
在 TanStack Start 中使用 VitePWA 的正确配置
javascript