如何利用 window.matchMedia 根据用户系统偏好自动切换网站的深色皮肤

作者:袖梨 2026-07-27
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',可据此降级处理
  • 避免同时查 lightdark 两个查询,只需查一个,另一个取反即可;查两个可能因浏览器实现差异导致逻辑冲突

如何监听系统主题切换事件

必须调用 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');  }});

容易忽略的点:

  • 旧版 Safari(≤13.1)不支持 addEventListener,需兼容 addListener 回退写法
  • 如果组件卸载(如 React unmount、Vue destroy)而没移除监听器,会造成内存泄漏;务必在清理阶段调用 media.removeEventListener('change', handler)
  • 该事件只在系统级主题变更时触发,不会因窗口缩放、刷新或 CSSOM 修改而触发

为什么 CSS 媒体查询 + JS 监听要配合使用

CSS 的 @media (prefers-color-scheme: dark) 能让首屏直出正确样式,但无法驱动 JS 逻辑(比如图表重绘、localStorage 记录、第三方库主题切换);而 JS 的 matchMedia 可以触发这些行为,但不能替代 CSS 原生渲染性能。

  • 默认把亮色变量写在 :root 顶层,深色覆盖写在媒体查询内;反过来会导致深色模式下变量未定义
  • 不要在组件内部重复定义同一组 CSS 变量,否则媒体查询无法穿透作用域生效
  • 若同时支持手动切换和系统偏好,建议用 localStorage 存储用户选择,并优先级高于系统值;否则用户点了“切亮色”,系统一变又回暗色,体验断裂

最易被绕过的细节:服务端渲染(SSR)或静态生成站点首次加载时,window 不存在,matchMedia 会报错;需要确保相关逻辑只在客户端执行,且首次渲染前就完成主题判定,否则会出现闪动(FOUC)。

相关文章

精彩推荐