直接用css变量配合@media (prefers-color-scheme: dark)可实现无js平滑夜间切换,需在:root定义默认变量并在媒体查询中完整覆盖,同时通过localstorage+class手动覆盖系统偏好,并确保读写时机早于样式计算。

如何用 CSS 变量 + prefers-color-scheme 实现平滑夜间切换
直接用系统偏好驱动主题,是最轻量、最可靠的方式。它不依赖 JS 切换状态,避免闪烁和首次渲染错乱,且能自动响应系统主题变更。
-
prefers-color-scheme: dark是媒体查询关键,必须写在 CSS 里,不能靠 JS 模拟 - 所有颜色值(包括
background、color、border-color)都要用var(--bg)这类 CSS 变量定义,否则无法动态更新 - 不要只写
@media (prefers-color-scheme: dark),得配合 :root 下的变量定义,例如::root { --bg: #fff; --text: #333; }<br>@media (prefers-color-scheme: dark) { :root { --bg: #121212; --text: #e0e0e0; } } - 若想让用户手动覆盖系统设置,需额外加一个 class(如
theme-manual-dark),并确保它的优先级高于媒体查询(比如用html.theme-manual-dark前缀)
localStorage 存储用户选择时的三个关键点
用户点了“深色”按钮,就得记住——但存错地方或读取时机不对,下次刷新就失效。
- 写入时机:在按钮点击后立刻调用
localStorage.setItem('theme', 'dark'),别等动画结束或异步回调 - 读取时机:必须在 CSS 变量初始化前执行,推荐放在
内联脚本中,或至少在开头立即运行,否则首屏会闪白/闪黑 - 变量同步逻辑要闭环:读取
localStorage→ 设置document.documentElement.className或style→ 触发 CSS 变量重计算;漏掉任一环,视觉就不一致
按钮交互容易踩的坑:不是所有 toggle 都叫“切换”
按钮文案和状态必须与实际 DOM 状态严格一致,否则用户会困惑“我明明点了,怎么没变?”
- 别用
aria-pressed="true/false"单独控制可访问性状态,它必须和真实 class 或属性同步,例如:button.setAttribute('aria-pressed', document.documentElement.classList.contains('dark')) - 禁用过渡动画干扰判断:如果深色模式切换靠
transition: background 0.3s,那按钮本身状态更新(如图标旋转、文字变“已开启”)必须在 transition 开始前完成,否则看起来“滞后” - 不要监听
click后再查当前主题——应先读取当前状态,再决定下一步设为什么,避免连点两次导致状态错乱
兼容性底线:哪些浏览器真不支持,哪些只是配置问题
prefers-color-scheme 在 Safari 12.1+、Chrome 76+、Firefox 67+ 支持良好,但 iOS 12.2 之前的 Safari 完全不识别。这不是 bug,是事实。
- IE 和旧 Edge(≤18)完全不支持
prefers-color-scheme,必须降级为纯 JS 控制,且不能依赖媒体查询 - 部分安卓 WebView(尤其 Android 7–8 的系统浏览器)对
prefers-color-scheme解析不稳定,建议加一层window.matchMedia('(prefers-color-scheme: dark)').matches运行时校验 - 如果你用了
CSS.supports('color-scheme', 'dark')做特性检测,请注意它返回 true 并不代表prefers-color-scheme已生效——它只表示语法支持,不保证媒体查询被监听
localStorage 读写时机、按钮状态与 DOM 的实时绑定——这三处稍有偏差,夜间模式就会“看起来开了,其实没开”或者“开了又闪回白天”。调试时盯住 document.documentElement.className 和 computed style 里的 --bg 值,比看按钮文案更准。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











