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

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

相关推荐
汪子熙27 分钟前
Angular 服务器端应用 ng-state tag 的作用介绍
前端·javascript·angular.js
Envyᥫᩣ36 分钟前
《ASP.NET Web Forms 实现视频点赞功能的完整示例》
前端·asp.net·音视频·视频点赞
Мартин.5 小时前
[Meachines] [Easy] Sea WonderCMS-XSS-RCE+System Monitor 命令注入
前端·xss
昨天;明天。今天。6 小时前
案例-表白墙简单实现
前端·javascript·css
数云界6 小时前
如何在 DAX 中计算多个周期的移动平均线
java·服务器·前端
风清扬_jd6 小时前
Chromium 如何定义一个chrome.settingsPrivate接口给前端调用c++
前端·c++·chrome
安冬的码畜日常6 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
ChinaDragonDreamer6 小时前
Vite:为什么选 Vite
前端
小御姐@stella6 小时前
Vue 之组件插槽Slot用法(组件间通信一种方式)
前端·javascript·vue.js
GISer_Jing6 小时前
【React】增量传输与渲染
前端·javascript·面试