现代 CSS 切换主题方式探索
css 相关loading...次阅读
最近重构了博客系统,其中涉及到主题切换的方案,于是就有了这次的分享。
UnoCSS 方案
使用 CSS 变量配合 UnoCSS 插件注入的方式完成主题切换功能。
具体来说,我们的主题有三种模式:
- 跟随系统;
- 浅色;
- 深色。
其中,用户手动选择的模式会覆盖系统跟随模式。
一个比较容易想到的方案是,在 :root
中定义一些 CSS 变量,例如:
:root {
--color: red;
--dark-color: #333;
}
但这样使用起来会有些麻烦,因为 UnoCSS 的使用方式主要是通过类名定义。
例如:
<div class="bg-[var(--color)] bg-[var(--dark-color)]">内容</div>
这样会导致两个变量同时存在的问题,同时也会丧失 UnoCSS 插件的提示功能。
针对这两个问题,可以通过类名覆盖的方式改进方案:
:root {
--color: red;
}
html.dark {
--color: #333;
}
<div class="bg-[var(--color)]">内容</div>
这样就可以通过 --color
根据主题切换颜色。不过,这个方案仍然存在一些不足之处:
- 缺少编辑器提示;
- 前缀
bg-[var(--color)]
过长。
使用 Theme 配置
UnoCSS 在 Theme 中提供了一个代码示例:
theme: {
colors: {
veryCool: '#0000ff', // class="text-very-cool"
brand: {
primary: 'hsl(var(--hue, 217) 78% 51%)', // class="bg-brand-primary"
DEFAULT: '#942192', // class="bg-brand"
},
},
}
通过扩展 colors
,可以直接引用变量。例如:
theme: {
colors: {
color: 'var(--color)',
},
}
然后结合如下 CSS 和 HTML:
<style>
:root {
--color: red;
}
html.dark {
--color: #333;
}
</style>
<div class="bg-color">内容</div>
这样解决了两个问题:
- 缩短了前缀;
- 提供了编辑器提示。
不过,这仍需一个单独的 CSS 文件来管理变量。是否有更好的方式来管理这些变量呢?
使用 Presets
观察以下 style
内容:
:root {
--color: red;
}
html.dark {
--color: #333;
}
我们可以通过 UnoCSS 的 Presets 注入这些信息。Presets
是 UnoCSS 的配置项,最终会合并到配置文件中。
例如,官方在 Preflights 中的一个示例:
preflights: [
{
getCSS: ({ theme }) => `
* {
color: ${theme.colors.gray?.[700] ?? "#333"};
padding: 0;
margin: 0;
}
`,
},
];
根据这个思路,我们可以定义一个 Preset,将 CSS 注入到页面中。例如:
import { themePreset } from "./plugins/theme-preset";
import { defineConfig, presetUno } from "unocss";
export default defineConfig({
presets: [
themePreset({
primary: {
light: "#0F7AE5",
dark: "#2B8EF0",
},
code: "#333",
}),
],
});
生成的 CSS 如下:
:root {
--code: #333;
--primary: #0f7ae5;
}
html.dark {
--primary: #2b8ef0;
}
使用方式保持不变,变量注入的工作由 UnoCSS 自动完成。
theme-preset.js 的部分示例:
{
theme: {
colors: {
code: 'var(--code)',
primary: 'var(--primary)',
},
},
preflights: [
{
getCSS: () => `
:root {
--code: #333;
--primary: #0F7AE5;
}
html.dark {
--primary: #2B8EF0;
}
`,
},
]
}
使用 light-dark 函数
今天阅读了一篇文章:CSS: Easy dark mode。文章介绍了一种新思路,通过 CSS 函数实现主题切换,无需依赖媒体查询。
示例代码:
:root {
color-scheme: light dark;
}
body {
color: light-dark(#333b3c, #efefec);
background-color: light-dark(#efedea, #223a2c);
}
注意:
color-scheme
是必须的。
不过 light-dark
兼容性还存在一些问题:
虽然现代浏览器支持良好,但若考虑兼容性,可以配合 postcss-dark-theme-class 使用。
安装插件:
npm install --save-dev postcss postcss-dark-theme-class
配置文件 postcss.config.cjs
:
module.exports = {
plugins: [require("postcss-dark-theme-class")],
};
样式文件 style.css
:
#app {
color: light-dark(black, white);
}
生成的 CSS 如下:
@media (prefers-color-scheme: dark) {
:where(html:not(.is-light)) #app {
color: white;
}
}
:where(html.is-dark) #app {
color: white;
}
@media (prefers-color-scheme: light) {
:where(html:not(.is-dark)) #app {
color: black;
}
}
:where(html.is-light) #app {
color: black;
}
这种方式生成的代码和 UnoCSS 类似,但增加了 :where
和 :not
的规则。
:where 是一个没有权重的选择器,:not 就是排除规则生效。
总结
对比两种方案:
- UnoCSS:需要结合工具实现,若切换主题的配置项较多,可能会显得繁琐。
- light-dark:写法更简单,但生成的 CSS 文件较大,且需注意兼容性问题。
每种方案都有其适用场景,可以根据实际需求选择。
如果考虑使用 UnoCss 方案需要查看完整代码,可以看下我的仓库文件。