可实现无JS主题切换,但需放弃手动切换和localStorage记忆,仅依赖prefers-color-scheme媒体查询自动响应;CSS变量必须在:root级用@media声明,Less通过globalVars将变量转为var()引用,首屏防闪需HTML内联脚本同步data-theme。

不能完全无 JS —— 但可以做到“JS 不参与主题逻辑”,仅用于防闪屏和 fallback 初始化。纯 Less 编译出的 CSS 本身无法响应 prefers-color-scheme,必须靠浏览器原生媒体查询驱动;而 Less 只负责把变量编译成静态 CSS 规则。真正“无 JS 主题切换”的前提,是放弃手动切换、放弃 localStorage 记忆、只做系统级自动跟随。
为什么 @media (prefers-color-scheme: dark) 必须写在 :root 级别
Less 编译后生成的是普通 CSS,prefers-color-scheme 媒体查询只有作用在 :root 上才能批量覆盖 CSS 自定义属性。如果写在 .card 或 button 里,变量不会被继承或穿透生效。
- 错误写法:
.card { @media (prefers-color-scheme: dark) { --bg: #1e1e1e; } }→ 编译后无效,变量作用域错乱 - 正确写法:在 Less 入口文件顶部统一定义
:root,再用媒体查询覆盖 - Less 中要显式写出两套值,不能依赖“运行时计算”——
var(--bg)的值在编译期就固定了,媒体查询只是替换声明位置
globalVars 是怎么把 Less 变量“转义”成 CSS 变量的
Webpack 的 less-loader 支持 globalVars 配置,它不是运行时注入,而是在 Less 解析前,把键值对当成“伪变量”前置插入。例如:
module.exports = {
module: {
rules: [{
test: /\.less$/,
use: [{
loader: 'less-loader',
options: {
globalVars: {
'bg-body': 'var(--bg-body)',
'text-primary': 'var(--text-primary)'
}
}
}]
}]
}
}
这样一段 Less:
立即学习“前端免费学习笔记(深入)”;
div { background: @bg-body; color: @text-primary; }
会被 less-loader 先重写为:
@bg-body: var(--bg-body);
@text-primary: var(--text-primary);
div { background: @bg-body; color: @text-primary; }
最终编译出的就是 div { background: var(--bg-body); color: var(--text-primary); } —— 所有样式都基于 CSS 变量,媒体查询才真正起作用。
图片、边框、阴影这些非颜色属性怎么适配
CSS 变量能控制的不只是 color 和 background-color。暗黑模式下容易被忽略的三类元素:
-
border-color:不要写死#ddd,改用var(--border),并在媒体查询中定义深色值(如#333) -
box-shadow:避免0 2px 4px rgba(0,0,0,0.1)这种硬编码,改用var(--shadow),深色模式下可设为0 2px 4px rgba(0,0,0,0.3)提升层次感 -
filter: brightness()或invert():慎用!这类滤镜会破坏图片语义、影响可访问性,且无法局部关闭。应优先用picture+media属性或background-image: url(var(--img-url))配合变量切换
首屏不闪屏的关键:HTML 中提前声明 data-theme
即使不用 JS 切换主题,也必须在 标签上同步设置初始状态,否则 Safari/Chrome 在 CSSOM 构建完成前会按默认浅色渲染一次,造成白闪。
最轻量做法是在 HTML 模板里内联极简脚本(不依赖框架、不触发重排):
<script>
document.documentElement.setAttribute(
'data-theme',
window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
);
</script>
然后在 CSS 中用属性选择器兜底:
[data-theme="dark"] { --bg-body: #121212; --text-primary: #e0e0e0; }
[data-theme="light"] { --bg-body: #ffffff; --text-primary: #333333; }
这样,媒体查询失效时(如旧版 Safari),仍能靠 JS 读取一次并 fallback 到合理状态;而现代浏览器则以媒体查询为准,两者不冲突。
真正难的不是写几行 Less,而是让所有颜色、边框、阴影、图标、第三方组件都统一走同一套变量体系。一旦某个 button 的 border 写死在组件内部,整个暗黑逻辑就漏气了。变量收口这件事,必须在项目初期就定死规范,后期补救成本极高。


















