仅用 不可行,因首屏渲染时 HTML 未被 JS 控制导致闪动,旧版 WebView 不支持该媒体查询,且无 fallback 机制易致样式丢失、层叠错乱。

直接用 <link media="(prefers-color-scheme: dark)"> 是最轻量、浏览器原生支持的方式,但必须配合 data-theme 属性初始化和 fallback 机制,否则首屏必闪、旧版 WebView 失效、第三方组件失色。
为什么不能只靠 <link media> 就完事
浏览器确实会按 media 条件决定是否下载 CSS,但问题出在“时机”和“兜底”上:
-
prefers-color-scheme的媒体查询在 HTML 解析阶段才生效,而元素此时还没被 JS 控制 —— 导致 Safari / Android WebView 首次渲染仍用浅色变量,等 CSS 加载完才重绘,出现白→黑闪动 - 微信内置 WebView(X5 内核)、iOS 12.5 及更早 Safari 完全忽略
media="(prefers-color-scheme: dark)",dark.css根本不加载 - 没有
onerror回调时,网络失败或路径错误会导致深色规则彻底丢失,页面文字糊成一片 - 多个
<link>并发加载时,CSS 层叠顺序不可控,dark.css里的button { color: white }可能被light.css后续同名规则覆盖
正确写法:三步闭环(检测 + 加载 + 同步)
必须把 JS 初始化、<link> 声明、样式作用域三者绑死,缺一不可:
- 在
最顶部插入轻量内联 JS:const mql = window.matchMedia('(prefers-color-scheme: dark)'); document.documentElement.setAttribute('data-theme', mql.matches ? 'dark' : 'light'); mql.addEventListener('change', e => { document.documentElement.setAttribute('data-theme', e.matches ? 'dark' : 'light'); }); -
<link>必须互斥且带data-theme标识:<link rel="stylesheet" href="/css/light.css" media="(prefers-color-scheme: no-preference), (prefers-color-scheme: light)" data-theme="light"> <link rel="stylesheet" href="/css/dark.css" media="(prefers-color-scheme: dark)" data-theme="dark">
- 所有样式规则必须用
[data-theme="dark"]前缀包裹,不能依赖媒体查询本身写样式:[data-theme="dark"] body { background: #121212; } [data-theme="dark"] .btn { color: #f1f1f1; }—— 这样即使dark.css加载失败,只要属性存在,内联 fallback 或 JS 变量仍可接管
移动端绕不开的三个硬坑
这些不是“可选优化”,而是不处理就必然失效的关键点:
立即学习“前端免费学习笔记(深入)”;
-
color-scheme: light dark必须声明在:root或<meta name="color-scheme">,否则<input>、<select></select>、滚动条等原生控件不会随主题变色,iOS 上尤其明显 - 第三方 UI 库(如 Vant、NutUI)默认不读
data-theme,得手动同步:document.documentElement.classList.toggle('van-dark', isDark)或监听其theme-changed事件反向设置属性 - 构建工具(如 Webpack/Vite)若对 CSS 做了 hash 或 code-splitting,确保
dark.css和light.css的路径在运行时绝对稳定 —— 路径错一个字符,onerror都捕获不到,用户就卡在白屏
真正麻烦的从来不是“怎么加 link”,而是让 data-theme 在 DOM ready 前就写进 ,让所有 CSS 规则从第一帧开始就受控。这点在 iOS Safari 和微信里差 10ms 都可能闪一下。别信“浏览器自动处理”。


















