使用 @media (prefers-color-scheme) 需显式声明 dark/light 规则对,CSS 变量应在各媒体查询内完整重定义,JS 须用 matchMedia().addEventListener('change') 监听切换,并为不支持环境提供三层 fallback。

如何用 @media (prefers-color-scheme) 检测系统主题并生效
直接写 @media (prefers-color-scheme: dark) 是最基础的入口,但它不会自动“同步”你的 CSS 变量或覆盖所有样式——必须显式声明每条规则。浏览器只按匹配的媒体查询块执行其中的声明,不继承、不回退、不智能合并。
常见错误是只在 :dark 块里改背景色,却忘了重置文字颜色、边框、阴影等依赖对比度的属性;或者把整套深色样式堆在同一个块里,导致维护困难。
- 必须成对定义:有
prefers-color-scheme: dark就该有对应的light(哪怕只是重置) - 不要依赖级联顺序“覆盖”,而是用媒体查询块明确控制作用域
-
prefers-color-scheme的值只有light、dark、no-preference三种,没有auto或system - 在 Chrome DevTools 的 Rendering 面板里可手动切换模拟,但 iOS Safari 需真机验证(部分旧版不支持
no-preference)
CSS 自定义变量(--color-bg)配合媒体查询的正确写法
把主题色抽成 CSS 变量本身没错,但很多人误以为只要在 :root 里定义一次、再在媒体查询里修改,就能全局响应——其实变量作用域取决于声明位置,:root 中的初始值不会被媒体查询里的同名变量“覆盖”,而是被“重新声明”为该块内的局部值。
真正可靠的做法是:在每个媒体查询块内完整声明你用到的所有相关变量,并确保它们出现在使用这些变量的选择器之前(CSS 自上而下解析)。
立即学习“前端免费学习笔记(深入)”;
:root {
--color-bg: #fff;
--color-text: #333;
}
@media (prefers-color-scheme: dark) {
:root {
--color-bg: #1e1e1e;
--color-text: #e6e6e6;
}
}
.card {
background: var(--color-bg);
color: var(--color-text);
}
注意:上面写法可行,是因为两个 :root 声明位于同一层作用域;若把 @media 块写在 :root 外部(比如放在某个组件类后面),变量可能未定义就已被使用。
JavaScript 如何监听主题变化并触发逻辑
仅靠 CSS 媒体查询无法触发 JS 行为(比如加载深色图标、上报用户偏好、初始化 canvas 颜色),必须用 window.matchMedia() 主动监听。
- 不能只调用一次
matchMedia('(prefers-color-scheme: dark)').matches判断当前状态——那只是快照 - 必须用
.addEventListener('change', handler)监听动态切换(如 macOS 用户在设置里实时切主题) - 首次执行时应主动调用 handler,避免首屏渲染和 JS 状态不一致
- iOS Safari 13.4+ 才支持
change事件;更老版本需轮询或降级为页面 reload
const darkModeMedia = window.matchMedia('(prefers-color-scheme: dark)');
function handleThemeChange(e) {
document.documentElement.setAttribute('data-theme', e.matches ? 'dark' : 'light');
}
handleThemeChange(darkModeMedia); // 初始化
darkModeMedia.addEventListener('change', handleThemeChange);
兼容性与 fallback:当 prefers-color-scheme 不可用时怎么办
不是所有环境都支持该特性:旧版 Edge、微信内置浏览器(X5 内核)、部分 Electron 应用默认禁用系统偏好读取。这时候单纯依赖 CSS 媒体查询会导致白屏、反色或不可读。
稳妥策略是「三层 fallback」:默认轻色 → JS 检测支持后挂载深色类 → 用户手动覆盖(如设置页开关)。CSS 层面至少保证默认样式可读,不依赖 JS 注入类名也能工作。
- 避免在
@media块外完全不定义关键颜色(比如只在dark里设background,light下留空) - 用
@supports (prefers-color-scheme: dark)包裹高级用法(如深色模式下的filter: invert(1)),防止不支持时解析失败 - 服务端渲染(SSR)场景下,无法直接读取客户端偏好,需通过 cookie / UA 特征或首屏 JS 注入 class 后重绘
最易被忽略的是打印样式表(@media print)与 prefers-color-scheme 的冲突——某些浏览器在深色系统下打印仍会强制使用深色背景,导致文字看不见。需要单独加 @media print { ... } 覆盖。


















