React 通过 CSS Variables 实现暗黑模式(一):项目搭建与核心逻辑

1,575 阅读3分钟

目前随着暗黑模式在各个系统的支持和推广下已经非常常见,浏览器相对应 API 也有较普遍的兼容性,并且通过 CSS Variables 现在可以方便的实现暗黑模式/白天模式样式切换,样式代码也利于维护不需要编写多份样式只需定义不同主题下的样式变量。

目前常见的实现暗黑模式的通用方案大概有以下几种,参考 CSS Tricks 文章

  • Using a Body Class (样式中每个 class 都写单独的样式,切换 class)
  • Using Separate Stylesheets (为每个主题维护不同样式文件)
  • Using Custom Properties(使用 CSS Variables,通过给 html 标签切换 data 属性,本文所采用的方案)

项目演示

ezgif.com-gif-maker.gif

技术

  • React - 本文的框架选择,Vue 也可以采用类似的方案,组件库选择和组件编写方式可能存在区别
  • Vite - 项目初始化及开发部署
  • React Vant - 组件库选择,其他组件库样式切换方案可能有区别需参考组件库文档
  • Less - 样式编写和维护

初始化项目

使用 Vite 初始化和开发部署项目,之前曾写过一篇 ViteReact 项目搭建的文章可做参考,项目搭建也非本文重点,所以这里不再详细描述项目搭建环节。该项目为手机端项目,预览调试需在手机模式。

# 创建项目
npm create vite@latest react-darkmode-demo -- --template react-ts
# 创建文件目录结构
cd src && mkdir -p assets assets/icons assets/images components constants pages pages/home styles utils
# 引入相关依赖
npm i react-vant
npm i -D less postcss-px-to-viewport
cd styles && touch css-variable.less global.less index.less react-vant.less variables.less common.less
# 工程化相关配置 (具体配置见 https://juejin.cn/post/7121685782980952101#heading-13)
npm i -D eslint eslint-config-react-app eslint-config-prettier prettier lint-staged rollup-plugin-visualizer @types/node@16 cross-env
npx husky-init && npm install
touch .eslintrc .eslintignore .prettierrc .prettierignore

具体配置可以参考项目源码该 Commit

核心逻辑

样式文件中定义好一些默认主题颜色的样式变量, 然后再定义通过 Javascript 切换为暗黑模式时的主题颜色的 样式变量。实际使用时只需在对应样式使用该样式变量即可,不需要写重复样式。

:root {
  --text-color: #222;
  --bkg-color: #fff;
  --anchor-color: #0033cc;
}

:root[data-theme=dark] {
  --text-color: #eee;
  --bkg-color: #121212;
  --anchor-color: #809fff;
}

body {
  color: var(--text-color);
  background: var(--bkg-color);
}
a {
  color: var(--anchor-color);
}
<button id="theme-button">切换主题</button>

以下是通过 Javascript 给 html 标签加上 data-theme 属性来切换不同主题的示例代码。

const btn = document.getElementById('theme-button')
const theme = localStorage.getItem('theme')
btn.addEventListener('click', () => {
  html.setAttribute('data-theme', theme === 'light' ? 'dark' : 'light');
})

也可通过媒体查询来定义不同主题样式,这样主题可以跟随系统选择自动切换而非手动切换主题可以更加灵活。

@media (prefers-color-scheme: dark) {
  /* Dark theme styles go here */
}

@media (prefers-color-scheme: light) {
  /* Light theme styles go here */
}
const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");

由于篇幅较长,第一篇描述该方案的核心逻辑和实现方案,下一篇中将详细描述如何在 React 项目中实现该逻辑并封装成通用和易于维护的组件。

参考链接