matchMedia 能监听系统主题变化是因为它返回的 MediaQueryList 对象支持 change 事件,用户切换深色模式时自动触发,无需轮询;需用 addEventListener 绑定,及时移除避免内存泄漏,并注意 SSR 和兼容性处理。

matchMedia 为什么能监听系统主题变化
因为 window.matchMedia 不只是查一次媒体查询结果,它本身支持事件监听——只要系统深色模式开关被用户手动切换(比如 macOS 的控制中心、Windows 设置、iOS 控制中心),匹配的 MediaQueryList 对象就会触发 change 事件。这比轮询 prefers-color-scheme 高效且准确。
监听 change 事件的正确写法
直接调用 matchMedia 返回的对象有 matches 属性和 addEventListener 方法,但注意:不能用 onchange 赋值(部分旧环境不兼容),必须用标准事件 API。
- 先执行
const media = window.matchMedia('(prefers-color-scheme: dark)') - 立即读取初始状态:
if (media.matches) { /* 当前是深色 */ } - 绑定监听:
media.addEventListener('change', e => { console.log(e.matches) }) - 别忘了在组件卸载或脚本清理时调用
media.removeEventListener('change', handler),否则可能内存泄漏
常见错误:监听后没更新页面样式
监听到 e.matches === true 只是告诉你系统切到了深色,但 DOM 并不会自动变。你需要主动应用样式:
- 给
<html>或<body>加 class:document.documentElement.classList.toggle('dark', e.matches) - 或者直接操作
style:document.body.style.backgroundColor = e.matches ? '#121212' : '#ffffff' - 如果用 CSS 变量,推荐统一在 :root 中定义,再用 class 切换作用域,避免内联样式覆盖困难
- 注意 SSR 场景(如 Next.js):服务端没有
window,首次渲染时matchMedia会报错,需加typeof window !== 'undefined'守卫
兼容性与降级处理要点
prefers-color-scheme 在 Chrome 76+、Firefox 67+、Safari 12.1+ 支持;IE 完全不支持。但 matchMedia 本身早在 IE10 就存在,只是不识别该媒体特性。
- 检查支持性:
if ('matchMedia' in window && window.matchMedia('(prefers-color-scheme)').media !== 'not all') - 不支持时,可 fallback 到 localStorage 记录用户上次手动选择的主题,或默认浅色
- 某些安卓 WebView(尤其旧版)可能不触发
change事件,建议加一个轻量级定时器兜底检测(比如每 2 秒查一次media.matches),但仅限必要场景
实际使用中最容易被忽略的是事件监听的移除时机和 SSR 环境下的执行保护——这两处出问题时,现象往往是“第一次切换正常,刷新后失效”或“开发环境 OK,部署后白屏”。


















