matchMedia() 是唯一原生支持动态响应设备特性变化的接口,返回可监听的 MediaQueryList 对象;需用 addEventListener("change") 主动响应 prefers-color-scheme 等变化,避免误用 resize 或服务端不一致导致闪烁。

matchMedia() 是监听设备特性变化的核心 API
JavaScript 中没有“媒体查询对象”的自动更新机制,matchMedia() 是唯一原生支持动态响应设备特性(如视口宽度、配色方案、横竖屏)变化的接口。它返回一个 MediaQueryList 对象,该对象可被监听,而不是只做一次判断。
常见误用是只调用 matchMedia("(max-width: 768px)").matches 拿个布尔值就结束——这无法响应后续变化。
-
matchMedia()返回的对象有matches属性(当前是否匹配)和addEventListener("change", handler)方法(推荐)或已废弃的addListener() - 必须手动绑定事件监听器,否则不会触发回调
- 监听器回调中应重新读取
event.matches或mediaQueryList.matches,不要依赖闭包里旧的判断结果
监听 prefers-color-scheme 切换暗色模式
用户在系统级切换深色/浅色主题时,prefers-color-scheme 媒体查询会变化,但页面不会自动重绘样式——需 JS 主动响应。
典型场景:切换主题后更新自定义组件状态、加载对应图标、修改内联样式或切换 class。
立即学习“Java免费学习笔记(深入)”;
const darkModeQuery = matchMedia("(prefers-color-scheme: dark)");
function handleColorSchemeChange(e) {
if (e.matches) {
document.body.classList.add("dark");
} else {
document.body.classList.remove("dark");
}
}
darkModeQuery.addEventListener("change", handleColorSchemeChange);
// 立即执行一次,确保初始状态正确
handleColorSchemeChange(darkModeQuery);
resize 事件不适合替代媒体查询监听
很多人试图用 window.addEventListener("resize", ...) 模拟断点逻辑,但这既不准确也不高效:
-
resize触发频率高,易造成性能问题;而matchMedia的change只在查询条件真/假切换时触发一次 -
resize无法感知非尺寸变化(如prefers-reduced-motion、hover、any-hover) - 移动端缩放、字体放大、阅读模式等也会改变媒体查询匹配状态,但不会触发
resize - 某些浏览器(如 Safari)在地址栏展开/收起时会触发
resize,但实际视口宽度未变,导致误判
注意 SSR 和 hydrate 时的不一致问题
服务端渲染(SSR)或静态生成(如 Next.js、Nuxt)环境下,matchMedia 在服务端不可用(无 window),且首次客户端 hydrate 时,matches 结果可能与服务端假设不一致,造成 UI 闪烁。
解决方案不是禁用 JS,而是延迟 hydrate 或用 CSS 优先兜底:
- 服务端默认不设主题类,让 CSS 中
@media (prefers-color-scheme: dark) { ... }控制基础样式 - JS 监听仅用于增强(如记录用户偏好、切换图标 fill 颜色),而非决定核心布局
- 若必须 JS 控制,可在
useEffect(React)或onMounted(Vue)中首次检查并同步,避免服务端硬编码匹配状态
真正难处理的是多层嵌套媒体查询 + 用户交互 + 服务端缓存组合下的状态漂移——这时候靠 JS 监听本身不够,得配合 localStorage 记录上一次有效匹配,并在页面加载初期快速比对修正。











