背景

最近给自己的歌词本加上了主题切换的功能,因为最近爱上了暗色模式,但是有的时候浏览网站突然被一个不支持暗色的网站亮瞎…访问自己的歌词本的时候也被亮瞎,才意识到网站缺少暗色模式的支持。

另外也并不打算只支持两种颜色的主题,由于原本的亮色主题是淡淡的黄色背景加上些许杂色,请教了@筱枫,取名「亚麻」,而另外两种暗色模式分别取名「黑曜」和「真黑」。设计上黑曜是打算做一个偏蓝色的暗色主题,而真黑是 rgb(0, 0, 0) 的纯黑色。(主要用于 OLED)

废话疑似有点多了…下面进入正题,谈谈在 TailwindCSS 和 Unocss 中实现多主题的方式。

实现

Unocss 这些原子化 CSS 工具的设计大体上参照的都是 TailwinCSS/WindiCSS,因此配置也类似,但不同之处在于它们支持透明度设定的方式。

而实现主题的方式则是将网站使用的颜色规范成特定命名的颜色,把它们定义在 CSS 变量中,然后更改配置以支持这些规范颜色的类名。

CSS 变量定义的方式如下:

1
2
3
4
5
6
7
8
9
:root, html[data-theme="ama"] {
--color-primary: 243 243 239; /* #FFFFFB */
/* ... */
}

html[data-theme="kokuyou"] {
--color-primary: 28 35 43; /* #1C232B */
/* ... */
}

这样通过切换 HTML 标签上的 data-theme 就可以影响内部通过 var 函数拿到的值,进而切换主题了。

然后在 TailwindCSS 中就可以这样配置:

1
2
3
4
5
6
7
8
9
module.exports = {
theme: {
extend: {
textColor: {
primary: 'rgb(var(--color-primary))',
}
}
}
}

这样任何的 text-primary 类就会被编译为 rgb(var(--color-primary)) 这样的颜色值,然后通过 CSS 变量拿到实际主题中的颜色值。

在原子化 CSS 中,一般还会注入一些局部使用的 CSS 变量以便通过其他 CSS 类控制其他属性的值,例如可以这样设置文字颜色并且调整文字颜色的透明度:

1
<div class="text-slate-700 text-opacity-50">Hello</div>

text-slate-700 编译出的其实是这样的 CSS:

1
2
3
4
.text-slate-700 {
--tw-text-opacity: 1;
color: rgb(51 65 85 / var(--tw-text-opacity, 1));
}

text-opacity-50 只需要控制 --tw-text-opacity 这个变量就可以了:

1
2
3
.text-opacity-50 {
--tw-text-opacity: 0.5;
}

而咱们自定义的颜色类则就不能应用透明度功能了,当然你配置成 primary: 'rgb(var(--color-primary) / var(--tw-text-opacity))' 理论上也是可以的,但有一些坏处:

  1. 太长了
  2. 依赖 Tailwind 的变量前缀,在其他原子化 CSS 中可能不一致(例如 Unocss 的前缀就是 --un
  3. 想不出来了(

总之 Tailwind 提供了官方的解决方案,尽量还是使用官方的解决方案,毕竟非官方提供的接口随时可能被意外改掉。Tailwind 支持在配置时传入一个函数,函数的第一个参数里面可以拿到透明度信息。

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
theme: {
extend: {
textColor: {
primary: ({ opacityValue }) => {
return `rgb(var(--color-primary) / ${opacityValue})`;
},
}
},
},
}

这样就可以支持自定义颜色的透明度设置了。

但是上述方法在 Unocss 中不奏效,咱找了很久文档也没看到说怎么自定义透明度,最后搜到了这个 issue,才知道原来 Unocss 直接内置了一个魔法,可以自动给用了 var() 的配置插入透明度,只需像不带透明度那样编写就可以,下面是 Unocss 的配置示例:

1
2
3
4
5
6
7
export default defineConfig({
theme: {
colors: {
primary: 'rgb(var(--color-primary))',
}
},
})

它会自动编译出类似这样的 CSS 类:(以文本为例)

1
2
3
4
.text-primary {
--un-text-opacity: 1;
color: rgb(var(--color-primary) / var(--un-text-opacity));
}

参考