必须将初始化脚本内联于最前面,否则页面必闪白或闪黑;因浏览器先加载css并绘制首屏,若js延后执行再设data-theme,已渲染的默认样式将发生视觉跳变。

暗黑模式不是“加个 class 就完事”,而是必须在 html 元素上用 data-theme 控制,且初始化脚本得塞进 最前面——否则页面必闪白或闪黑。
为什么必须把初始化脚本写在 里?
浏览器渲染是流水线:加载 CSS → 解析样式 → 绘制首屏。如果等 DOMContentLoaded 再读 localStorage 并设主题,页面已按默认浅色渲染完毕,再切主题就会视觉跳变。
- 脚本必须是内联的,不能是外部
<script src="..."></script> - 必须放在所有
<link rel="stylesheet">标签之前 - 操作目标只能是
document.documentElement,不是document.body(否则::-webkit-scrollbar、input::-webkit-inner-spin-button等伪元素失效)
data-theme 和 CSS 变量怎么联动才不失效?
CSS 变量本身不会响应属性变化,必须靠选择器触发重计算。很多“点了按钮没反应”的问题,根源是选择器漏了 html 前缀或权重不够。
- 必须写成
html[data-theme="light"] { --bg: #fff; }和html[data-theme="dark"] { --bg: #121212; },漏掉html前缀就匹配失败 - 不能只靠
@media (prefers-color-scheme: dark)定义变量——它不响应 JS 切换,刷新即回退 -
:root里不要直接写颜色值,所有可变样式都得用var(--bg),包括background-color、color、border-color、box-shadow
手动切换按钮和系统偏好怎么共存?
用户点了“切暗色”,不是希望系统一改设置它就跟着变;也不是点了之后刷新又回到系统状态。三者优先级必须明确:
- 第一优先级:
localStorage.getItem('theme')(用户上次手动选的) - 第二优先级:
window.matchMedia('(prefers-color-scheme: dark)').matches(系统当前设置) - 第三优先级:硬编码
'light'(兜底) - 每次切换后,必须同步更新
document.documentElement.dataset.theme和localStorage.setItem('theme', ...) - 还要改
<meta name="color-scheme" content="light dark">,否则 Chrome/Safari 地址栏、表单控件颜色不随页面变
监听系统偏好变更时容易踩哪些坑?
window.matchMedia 返回的是一个 MediaQueryList 实例,它不会自动重执行。只调一次 .matches 是不够的。
- 必须显式调用
addEventListener('change', handler),且要在初始化之后立即注册 - 回调里要同步更新
dataset.theme和localStorage,否则下次刷新仍沿用旧值 - 避免重复注册:保存
matchMedia实例引用,销毁前先removeEventListener(尤其在热重载场景) - 别让它当主控——
localStorage有值时,系统变更不应覆盖用户选择
最常被忽略的点:CSS 中所有主题变量必须定义在 :root,子选择器里重定义无效;meta[name="color-scheme"] 必须手动同步,否则浏览器 UI 和页面主题割裂。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











