应使用 window.matchmedia('(prefers-color-scheme: dark)').addeventlistener('change', handler) 监听系统深色模式切换,初始化时需主动执行 handler 确保首屏正确,并通过 data-theme 属性统一管理主题状态,避免直接操作内联样式。

matchMedia() 怎么监听系统深色模式变化
直接用 window.matchMedia('(prefers-color-scheme: dark)') 创建媒体查询对象,它不是一次性判断,而是支持持续监听——关键在于调用它的 addEventListener() 方法(注意:不是 addListener(),后者已废弃且在 Safari 14+ 和 Chrome 84+ 中失效)。
常见错误是只调用 matches 属性读取当前状态,却没绑定事件,导致页面加载后无法响应系统级切换。
- 必须用
addEventListener('change', handler),传入函数处理逻辑 - 初始化时要主动执行一次 handler,确保页面刚加载就应用正确主题
- handler 函数里别直接操作 DOM 样式,优先通过切换
或的 class(如dark)来解耦样式
为什么 addEventListener 比 onchange 更可靠
onchange 属性赋值方式(如 mediaQuery.onchange = handler)虽然能工作,但存在两个实际问题:一是无法多次绑定(后赋值会覆盖前一个),二是不兼容某些旧版 Chromium 内核的 WebView 场景;而 addEventListener 支持多监听器共存,也更符合现代 DOM 事件规范。
另外,change 事件只在匹配状态真正翻转时触发(从 false → true 或 true → false),不会在页面重绘、滚动等无关时机误发,比轮询 matches 健壮得多。
- 不要写
mediaQuery.onchange = () => {...} - 务必用
mediaQuery.addEventListener('change', e => {...}) - 记得在不需要时调用
removeEventListener(比如组件卸载时),避免内存泄漏
如何让 CSS 主题配色随 JS 切换自动生效
JS 控制主题,核心是让 CSS 能感知并响应这个状态。最稳妥的做法是:JS 只负责加/删 class,所有颜色定义全由 CSS 完成。
例如,在 上添加 data-theme="dark" 属性,然后用属性选择器写样式:
html[data-theme="dark"] {
--bg: #1e1e1e;
--text: #e6e6e6;
}
html:not([data-theme="dark"]) {
--bg: #ffffff;
--text: #333333;
}
这样既避免内联样式污染,又方便后续扩展(比如增加 light/dark/auto 三态)。
- 别用
document.body.style.backgroundColor这类硬编码方式 - 推荐统一用
document.documentElement.setAttribute('data-theme', 'dark') - 如果用了 CSS-in-JS 库(如 Emotion),确认其支持动态主题注入,否则仍建议走 class/data-attr 方案
兼容性与移动端要注意什么
window.matchMedia 在 iOS Safari 10.3+、Android Chrome 56+ 已完全支持,但有个隐藏坑:iOS 系统设置里“深色模式”开关打开后,部分老版本 Safari(如 iOS 13.3)需要用户手动锁屏再解锁,才会触发 change 事件——这不是代码问题,是系统行为。
另外,微信内置浏览器(X5 内核)和部分国产安卓浏览器可能根本不支持 prefers-color-scheme,此时 mediaQuery.matches 会恒为 false,需 fallback 到 localStorage 记录用户手动选择的主题。
- 永远检查
mediaQuery.media是否为'(prefers-color-scheme: dark)',防止降级失败 - 在监听前先做一次
if (window.matchMedia)判断 - 用户手动切换主题时,应同时更新 localStorage 并强制同步到 DOM,绕过系统检测限制
真正麻烦的不是监听本身,而是当用户既开了系统深色模式,又在网站内手动选了浅色主题时,如何优雅地分层优先级——这时候得靠数据状态管理,而不是只靠 matchMedia。











