最稳妥方式是用@media (prefers-color-scheme: dark)直接覆盖:root中的HSL变量,需完整重写所有分量(如--bg-h、--bg-s、--bg-l),并确保单位规范(如70%不可加引号)、无空格调用hsl(var(--h),var(--s),var(--l)),手动切换时须结合localStorage与user-dark类同步更新变量和状态。

监听 prefers-color-scheme 并在 :root 中重定义 HSL 变量
直接用媒体查询覆盖 :root 里的 HSL 变量是最轻量、最可靠的注入方式。系统偏好变化时,浏览器会自动重计算所有依赖这些变量的样式,无需 JS 干预。
-
@media (prefers-color-scheme: dark)内必须重写全部要用到的 HSL 分量变量(--bg-h、--bg-s、--bg-l等),不能只改其中一两个——未被覆盖的变量仍沿用浅色模式值,容易导致对比度崩坏或颜色错位 - 深色模式下别机械地把
--bg-l从95%改成5%:OLED 屏上hsl(0, 0%, 4%)比hsl(0, 0%, 8%)视觉差异极小,但前者更易引发眼疲劳;推荐区间是4%–8% - 文字颜色别只调
--text-l,要同步检查 WCAG 对比度:比如hsl(210, 10%, 20%)在hsl(210, 0%, 6%)背景上对比度约 15:1,安全;但若背景用了hsl(0, 0%, 2%),同一文字就只剩 7:1,需把文字明度提到25%以上
用户手动切换时如何覆盖系统偏好?
当用户点击「深色模式」按钮,而系统设为浅色时,prefers-color-scheme 媒体查询不会触发。此时必须用 JS 主动注入变量,并加 class 标记状态,否则样式层和逻辑层会脱节。
- 首次加载时读取
window.matchMedia('(prefers-color-scheme: dark)').matches,存入localStorage;后续所有切换都以该存储值为准,而非实时查媒体查询 - 切换时执行:
document.documentElement.style.setProperty('--bg-l', '6%');,同时给<html>加 classuser-dark,并在 CSS 中写.user-dark:root { --bg-l: 6%; }—— 这样能确保 SSR 渲染和客户端 JS 同步,避免 FOUC - 不要只靠 JS 改变量:如果没配套 class 或媒体查询,服务端渲染的初始 HTML 里
:root还是浅色值,JS 注入后样式会闪一下;class 是兜底信号
hsl() 中使用 var() 的单位陷阱
变量值带不带 %、空格怎么放,直接决定 hsl() 能否解析成功。旧版 Safari 尤其敏感,不是“偶尔失效”,而是“稳定报错”。
-
--bg-s: 70%;✅,--bg-s: "70%";❌(引号会让变量变成字符串,hsl()拒绝解析) -
hsl(var(--bg-h),var(--bg-s),var(--bg-l))✅(无空格最稳),hsl(var(--bg-h), var(--bg-s), var(--bg-l))❌(Safari 16.4 以下直接忽略整条声明) - 饱和度和明度变量必须始终带
%单位,哪怕你在calc()里算:calc(var(--bg-s) * 0.8)得到的是56%,不是56;写成calc(var(--bg-s) * 0.8%)会因单位重复失效
Shadow DOM 组件里变量突然不生效?
变量没继承进来,不是 CSS 写错了,是作用域断了。Shadow DOM 默认不继承 :root 变量,除非显式透传或重定义。
立即学习“前端免费学习笔记(深入)”;
- 在组件根节点(如
:host)里重新声明::host { --bg-l: var(--bg-l, 95%); },把全局值透进来 - 别在 Shadow DOM 内部再写
:root { --bg-l: 6%; }:这只会覆盖组件内局部:root,不影响外部,且容易和全局冲突 - 如果组件用
attachShadow({ mode: 'closed' }),变量透传必须靠 JS 手动 setProperty 到 shadowRoot.host,CSS 无法自动穿透
实际调试时,打开 DevTools 颜色拾取器,点开颜色小方块 → 切到 HSL 模式 → 拖动滑块看实时三值变化,比盯着代码猜靠谱得多。HSL 不是线性空间,尤其低明度区,人眼分辨力骤降,数值差 2% 可能根本看不出区别,差 5% 却显得脏——得靠眼睛校准,不是靠公式。


















