window.matchMedia 能实时响应系统深色模式切换,但必须显式绑定 change 事件监听器;直接读取 matches 属性仅获当前快照,不自动更新;需配合 CSS @media 和客户端安全执行以避免闪动与内存泄漏。
window.matchMedia 能实时响应系统深色模式切换,但必须显式绑定 change 事件监听器——不加这句,页面加载后就再也不会感知系统主题变化了。
直接读取 matches 属性即可,它返回布尔值:
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
注意:这个值只反映「当前快照」,不是实时状态。比如用户在页面打开后切到深色模式,isDark 不会自动变,除非你监听了 change 事件并重新读取。
document.body.classList.contains('dark') 来反推系统偏好,那只是你上次 JS 设置的结果prefers-color-scheme(如旧版 Safari 或 IE),window.matchMedia('(prefers-color-scheme: dark)') 仍会返回一个对象,但其 media 属性值为 'not all',可据此降级处理light 和 dark 两个查询,只需查一个,另一个取反即可;查两个可能因浏览器实现差异导致逻辑冲突必须调用 addEventListener('change', handler),且推荐在初始化时就绑定,而不是等用户点击按钮后再绑:
const media = window.matchMedia('(prefers-color-scheme: dark)');media.addEventListener('change', e => { if (e.matches) { document.documentElement.setAttribute('data-theme', 'dark'); } else { document.documentElement.setAttribute('data-theme', 'light'); }});
容易忽略的点:
addEventListener,需兼容 addListener 回退写法media.removeEventListener('change', handler)
CSS 的 @media (prefers-color-scheme: dark) 能让首屏直出正确样式,但无法驱动 JS 逻辑(比如图表重绘、localStorage 记录、第三方库主题切换);而 JS 的 matchMedia 可以触发这些行为,但不能替代 CSS 原生渲染性能。
:root 顶层,深色覆盖写在媒体查询内;反过来会导致深色模式下变量未定义localStorage 存储用户选择,并优先级高于系统值;否则用户点了“切亮色”,系统一变又回暗色,体验断裂最易被绕过的细节:服务端渲染(SSR)或静态生成站点首次加载时,window 不存在,matchMedia 会报错;需要确保相关逻辑只在客户端执行,且首次渲染前就完成主题判定,否则会出现闪动(FOUC)。