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

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

相关推荐
九九落1 分钟前
JavaScript 实现北京时间精确到毫秒显示:UTC+8、Asia/Shanghai 与在线时间校准详解
开发语言·javascript·ecmascript
葡萄城技术团队2 分钟前
一个单元格放置两个日期选择器:用 SpreadJS CellButtons 录入日期范围
前端
FEF前端团队1 小时前
小程序微信支付 V3 接入实战手册:从商户配置到前后端落地
javascript·后端·node.js
马可家的菠萝1 小时前
自动保存已经有了,为什么笔记软件还需要“历史版本”?
前端·后端·架构
半个落月1 小时前
从 "use client" 到 Route Handler:用 Todos 理解 Next.js 水合与全栈请求
前端·react.js·next.js
qq_452396232 小时前
第七篇:《大型前端项目的模块化与目录结构设计》
前端
你脑门上的脚印2 小时前
Vue 项目从零实现语音转文字、文字转语音功能(完整可用 + 踩坑指南)
前端·vue.js
paopaokaka_luck2 小时前
基于springboot3+vue3的车间生产管理系统(Echarts图形化分析、BI报表)
java·前端·spring boot·学习·echarts
程序员鱼皮2 小时前
3 大 DeepSeek Harness 进阶玩法,招多个大肥鱼帮我干活!
前端·后端·ai编程
KoPa2 小时前
HeySmart:大模型开源网关基座-请求生命周期与钩子引擎
前端·后端