移动端主题切换需三步:变量统一定义在:root;js提前检测prefers-color-scheme并设置data-theme防闪屏;第三方组件选择器需具体化且禁用!important。

移动端主题切换不是加个 prefers-color-scheme 媒体查询就能跑通的——90% 的闪屏、样式失效、第三方组件不响应,都卡在三处硬伤上:变量没挂到 :root、系统偏好没同步初始化、第三方选择器优先级压不过你的规则。
所有变量必须定义在 :root,不能写在 .dark 或 body 里
CSS 变量靠继承传播,只有 :root(即 元素)能保证全页面任意节点——包括 <input>、<button></button>、伪元素、Shadow DOM 内部——都能读到。写在其他选择器里,变量只在该选择器后代生效。
-
.dark { --bg: #121212 }→ 只对.dark自身及其子元素有效,兄弟节点和表单控件拿不到 -
body { --text: #eee }→var(--text)在上求值时根本找不到定义 - 正确写法是两套变量统一收口:
:root定义默认值,[data-theme="dark"]覆盖深色值,漏一个 fallback 就会崩
必须用 JS 提前检测并设置 data-theme,不能等媒体查询生效
移动端闪屏(白→黑)的根本原因是:浏览器首次渲染时,prefers-color-scheme 还没读取完成,:root 变量已按浅色默认值渲染完毕;等媒体查询生效才重绘——这个间隙在 iOS Safari 和 Android WebView 中尤其明显。
- 在
内联轻量 JS:window.matchMedia('(prefers-color-scheme: dark)').matches立刻检测并执行document.documentElement.setAttribute('data-theme', 'dark') - 所有关键样式规则要写两套:
[data-theme="dark"] .btn(防闪) +@media (prefers-color-scheme: dark) .btn(保语义) - 别指望服务端预判——首屏直出无法知道客户端系统设置,JS 初始化必须在 DOM 构建前完成
第三方组件主题失效,本质是选择器优先级不够
Ant Design、NutUI、Vant 这类库通常靠 JS 注入 .dark 类或动态改 :root 变量,和你的媒体查询规则容易打架:要么你的 CSS 被 reset,要么库的主题开关失效。
- 把选择器写具体:
html[data-theme="dark"] .nut-button,而不是简单写.nut-button - 禁用
!important——它会切断变量链,导致后续用户手动切换主题完全失效 - 查库文档是否提供
theme-changed事件,必要时监听并手动同步data-theme属性 - NutUI 的
.nut-theme-dark类需配合--nut-xxx变量使用,不能只加 class 不重载变量
transition 对变量名本身无效,只能作用于具体属性
写了 transition: --theme-color-primary 0.3s 是没用的——CSS 的 transition 不支持变量名作为过渡属性,浏览器直接忽略,连警告都不报。
- 真正生效的方式是:把变量用在某个支持过渡的属性上,再对该属性加
transition,比如background-color: var(--theme-color-primary); transition: background-color 0.3s ease - 只对
color、background-color、opacity、transform等原生可插值的属性加transition - 避免在伪元素(
::before)里用变量 +transition,部分浏览器渲染有延迟
最易被忽略的是 fallback 和 color-scheme 声明:每个 var(--xxx, #fff) 都得带后备值,且必须在 :root 加 color-scheme: light dark,否则 iOS Safari 会拒绝应用深色背景色。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











