前言
兄弟们五一快乐呀,最近在做暗色模式的需求,正好调研了一下比较常见的切换主题色的方案,简单分享一下
方案
arco design 处理方案
可能有同学没用过,就是更漂亮一些的 ant design,是我们项目中的组件库。
先来看看官方文档是如何介绍暗色切换的

很简单,直接去操控 arco-theme 这个属性的值,如果为 dark 就为暗色,那么就上项目里看看这个值

果然,这里增加了一个属性,但为什么增加一个属性就可以起到切换主题色的作用呢?再把目光切到右边,亮色模式时:

而如果切换到暗色模式

这下就清晰了,body 相关的样式有二,一个是 body { *** },另一个是 body[arco-theme=dark] { *** },正常情况下只会生效 body,而当使用这段代码时:
javascript
document.body.setAttribute('arco-theme', 'dark');
body 标签上会增加一段 arco-theme="dark",此时 body[arco-theme=dark] { *** } 将被匹配到,又因为多了一个筛选器,优先级会更高一级,这么一来就能覆盖掉最初的 body { *** },达成暗色模式。
话又说回来了,为什么切换一个 body 就可以进行全部样式的覆盖呢?
这就得说起 css 变量 这个东西了,它是在 2017 年基本全面支持的 css 特性,我们可以用 -- 语法来定义变量:
css
--xxxx: xxxxxx
--color-bg-1: #17171a;
--color-bg-2: #232324;
随后可以使用 var() 语法来使用变量
css
color: var(--color-bg-2);
如此一来,我们只需要在 body 中定义变量,那么 body 下的所有标签均可以使用该变量。当我替换了 body 中变量的定义时,下面所有标签的变量也会进行对应的替换。
直接听可能有点抽象,我们来写一个简单的 demo
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>主题色</title>
<style>
/* 设置默认主题 */
body {
--color-bg: white;
--color-text: black;
}
/* 设置暗色主题 */
body[data-theme='dark'] {
--color-bg: black;
--color-text: white;
}
/* 设置主内容区域 */
.main {
background-color: var(--color-bg);
color: var(--color-text);
}
</style>
</head>
<body>
<div class="main">
<h1>Hello World</h1>
<button onclick="toggleTheme()">Toggle Theme</button>
</div>
<script>
function toggleTheme() {
document.body.dataset.theme =
document.body.dataset.theme === 'dark' ? 'light' : 'dark';
}
</script>
</body>
</html>

如图所示,还是比较简单好懂的。arco design 做的事情基本就是如此,它定义了相当丰富的变量,所有的颜色几乎都可以在变量中找到。在组件库中,只需要全部颜色相关的 css 都使用 body 中的变量,就可以达成一键切换的效果。
不过我们自己开发的组件往往都是随心所欲的,左一个 bg-black ,右一个 #ffffff,这样搞就会出现无法切换的情况,所以需要将这些组件的 color 统一改造为使用 body 中对应的变量。
ant design 处理方案
老规矩,先看它官方文档

看起来是把逻辑都收敛起来了,只暴露了方法,我们从控制台再找找线索

藏的比较深,不过还是找到了。简单测试一下,当点击切换主题时,对应的变量改变了,但是其 css 类名并没有发生改变,奇怪,这点跟 arco 不一样,它又是如何实现的呢。
没办法,翻一下源码吧,先看一下暗色的演示代码:
js
const App: React.FC = () => (
<ConfigProvider
theme={{
// 1. 单独使用暗色算法
algorithm: theme.darkAlgorithm,
// 2. 组合使用暗色算法与紧凑算法
// algorithm: [theme.darkAlgorithm, theme.compactAlgorithm],
}}
>
<Space>
<Input placeholder="Please Input" />
<Button type="primary">Submit</Button>
</Space>
</ConfigProvider>
);
所以突破口应该在 ConfigProvider 的 theme 上,沿着这段代码一直翻源码,可以翻出以下逻辑
- 读取写入 theme 中的 token,并找到默认 token 进行合并,变为完整的 Seed Token
- 读取算法,利用该算法,将完整的 Seed Token 转为对应主题的 Map Token
- 将 Map Token 转化为 css 变量,并进行应用
非常复杂啊朋友们,所以这里就不展开说了,大致就是通过 js 去改造 css 变量
light-dark
light-dark 是近几年新出的 css 函数,直接看 demo 吧
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>light-dark Demo</title>
<style>
body {
background-color: light-dark(white, black);
color: light-dark(black, white);
}
</style>
</head>
<body>
<h1>Hello, world!</h1>
<p>跟随系统浅色/深色模式</p>
</body>
</html>
这个函数比较简单,但是它不能用 js 去手动切换,只能跟随系统。那可玩性就不高了
民间一行代码
css
body { filter: invert(1) hue-rotate(180deg) }
- 单独使用
invert(1)
会让白变黑,但也会让蓝变橙、红变绿等颜色被颠倒; - 加上
hue-rotate(180deg)
能够部分校正颜色的色调错乱,看起来更自然一些。

对于部分网站可用,但是大部分的时候效果并不好。
over,这就是常见的几种方案了,祝大家五一快乐~