matchMedia() 是唯一原生支持动态响应设备特性变化的接口,返回可监听的 MediaQueryList 对象;需用 addEventListener("change") 主动响应 prefers-color-scheme 等变化,避免误用 resize 或服务端不一致导致闪烁。
JavaScript 中没有“媒体查询对象”的自动更新机制,matchMedia() 是唯一原生支持动态响应设备特性(如视口宽度、配色方案、横竖屏)变化的接口。它返回一个 MediaQueryList 对象,该对象可被监听,而不是只做一次判断。
常见误用是只调用 matchMedia("(max-width: 768px)").matches 拿个布尔值就结束——这无法响应后续变化。
matchMedia() 返回的对象有 matches 属性(当前是否匹配)和 addEventListener("change", handler) 方法(推荐)或已废弃的 addListener()
event.matches 或 mediaQueryList.matches,不要依赖闭包里旧的判断结果用户在系统级切换深色/浅色主题时,prefers-color-scheme 媒体查询会变化,但页面不会自动重绘样式——需 JS 主动响应。
典型场景:切换主题后更新自定义组件状态、加载对应图标、修改内联样式或切换 class。
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);
很多人试图用
window.addEventListener("resize", ...) 模拟断点逻辑,但这既不准确也不高效:
resize 触发频率高,易造成性能问题;而 matchMedia 的 change 只在查询条件真/假切换时触发一次resize 无法感知非尺寸变化(如 prefers-reduced-motion、hover、any-hover)resize
resize,但实际视口宽度未变,导致误判服务端渲染(SSR)或静态生成(如 Next.js、Nuxt)环境下,matchMedia 在服务端不可用(无 window),且首次客户端 hydrate 时,matches 结果可能与服务端假设不一致,造成 UI 闪烁。
解决方案不是禁用 JS,而是延迟 hydrate 或用 CSS 优先兜底:
@media (prefers-color-scheme: dark) { ... } 控制基础样式useEffect(React)或 onMounted(Vue)中首次检查并同步,避免服务端硬编码匹配状态真正难处理的是多层嵌套媒体查询 + 用户交互 + 服务端缓存组合下的状态漂移——这时候靠 JS 监听本身不够,得配合 localStorage 记录上一次有效匹配,并在页面加载初期快速比对修正。