核心是用document.body.classlist.toggle('dark')切换class,css通过:root和body.dark :root定义变量实现主题切换,配合localstorage持久化用户选择,避免直接操作style或重复写样式。

怎么用 document.body.classList.toggle() 切换夜间模式
核心就一行 JS:用 classList.toggle() 切换一个统一的 class(比如 dark),然后靠 CSS 控制所有颜色变量。别写两套完全独立的样式表,维护成本高、容易漏改。
常见错误是直接操作 style 属性或反复 setAttribute,结果覆盖了其他 class,或者没触发 CSS 变量重计算。
- 推荐给
添加初始 class:或不设,默认按 light 处理 - 切换逻辑只管
dark这个 flag:document.body.classList.toggle('dark') - 务必在 CSS 里用
:root定义 light 主题色,再用body.dark :root覆盖——不是写两套完整 CSS
为什么必须用 :root + body.dark :root 而不是 .dark .text
因为 CSS 自定义属性(--color-text)作用域在 :root,且只在声明处生效;如果用 .dark .text { color: #333 },每个元素都得单独写,一加新组件就得补一堆 dark 规则,根本不可持续。
真实场景下,你可能有按钮、卡片、输入框、代码块……全靠继承 --color-text、--bg-surface 这类变量才可控。
立即学习“前端免费学习笔记(深入)”;
-
:root定义默认值::root { --color-text: #1a1a1a; --bg-surface: #fff; } -
body.dark :root覆盖变量:body.dark :root { --color-text: #e0e0e0; --bg-surface: #121212; } - 所有组件用
color: var(--color-text),无需额外 class
prefers-color-scheme 怎么和手动切换共存
用户系统设了暗色,但网页上又点了“切回亮色”,这时候不能让媒体查询覆盖掉用户主动选择——得用 JS 优先级兜底。
典型坑是只监听 prefers-color-scheme,却没存用户手动选择的状态,导致刷新后回到系统偏好,违背用户意图。
- 用
localStorage记录用户选择:localStorage.setItem('theme', 'dark') - 初始化时优先读 localStorage,没值再查
window.matchMedia('(prefers-color-scheme: dark)').matches - 监听系统变化时,仅当用户没手动设置过才同步更新 body class
为什么 transition: background-color .2s 在夜间模式里大概率失效
因为你在切 body.dark,但真正变色的是无数个用了 var(--bg-surface) 的子元素;CSS transition 不会响应自定义变量变化,它只认具体属性(如 background-color)的数值变更。
想加过渡效果,得让颜色变化落在可动画的属性上,而不是靠变量“间接”变。
- 方案一:对关键容器(如
main、header)单独加 transition,并确保它们的background-color直接写死变量(background-color: var(--bg-surface))——部分浏览器支持 - 方案二:用
@property声明受控变量(Chrome 110+),但兼容性差,生产环境慎用 - 更稳做法:放弃全局背景渐变,只对图标、边框等小元素做
color过渡,视觉影响小、兼容性好
复杂点在于,你以为切了个 class 就万事大吉,其实 theme 状态要横跨 JS 初始化、用户操作、系统监听、持久化、CSS 变量作用域、动画触发条件——少一环,夜间模式就“卡”在某个状态里出不来。











