移动端怎么监听系统暗黑模式切换并动态更新UI?

馨予 Dev 阅读 29

我在做移动端页面,想根据系统暗黑模式自动切换主题色,但用prefers-color-scheme媒体查询只在页面加载时生效,切换系统主题后页面没反应,咋办?

试过加matchMedia监听,但不知道是不是写法不对,完全没触发。有没有人遇到过类似问题?

const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', (e) => {
  console.log('theme changed', e.matches);
});
我来解答 赞 5 收藏
二维码
手机扫码查看
1 条解答
Top丶树萱
第一步,先说你代码的问题在哪。你写的 matchMedia 监听写法本身没问题,但很多移动端浏览器(尤其是 iOS Safari)对 prefers-color-scheme 的 change 事件支持是延迟的,或者需要页面处于激活状态才能触发。更关键的是——你得确认监听事件的写法是不是真的“挂上去了”。

我们先来验证一下:先别急着加逻辑,先看事件到底有没有触发。

你可以先写个最简单的测试页,打开手机浏览器访问,然后切系统暗黑模式,看控制台有没有输出:

const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

// 先查一次当前状态,确认支持
console.log('当前是否暗黑模式:', mediaQuery.matches);

// 用 addListener 还是 addEventListener?注意:标准是 addEventListener,但 Safari 早期只认 addListener
// 为了兼容性,最好两种都挂上(或者做一次兼容封装)
if (mediaQuery.addEventListener) {
mediaQuery.addEventListener('change', (e) => {
console.log('标准 change 事件触发了:', e.matches);
});
}

if (mediaQuery.addListener) {
mediaQuery.addListener((e) => {
console.log('旧版 addListener 触发了:', e.matches);
});
}


注意啊,这里有个坑:Safari(包括 iOS Safari 和 macOS Safari)在早期版本里只支持 addListenerremoveListeneraddEventListener 是后来才加的。虽然新版本 Safari 已经支持 addEventListener,但为了稳,建议两个都加,或者做一次兼容封装。

第二步,写个兼容性更强的监听函数,把主题切换逻辑抽出来:

function listenColorSchemeChange(callback) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

// 先执行一次回调,初始化主题
callback(mediaQuery.matches);

// 挂标准事件(现代浏览器)
if (mediaQuery.addEventListener) {
mediaQuery.addEventListener('change', (e) => callback(e.matches));
}

// 挂旧版事件(兼容 Safari 早期版本)
if (mediaQuery.addListener) {
mediaQuery.addListener((e) => callback(e.matches));
}
}


然后你就可以这样用:

listenColorSchemeChange(isDark => {
if (isDark) {
// 切暗黑主题:比如加个 class,或者改 CSS 变量
document.documentElement.classList.add('dark');
// 或者:document.documentElement.style.setProperty('--bg-color', '#121212');
} else {
document.documentElement.classList.remove('dark');
}
});


第三步,别忘了在 CSS 里配合使用变量或 class 控制样式。比如你可以在 上加 dark 类,然后这样写 CSS:

:root {
--bg-color: #ffffff;
--text-color: #333333;
}

.dark {
--bg-color: #121212;
--text-color: #eeeeee;
}

body {
background-color: var(--bg-color);
color: var(--text-color);
}


或者直接用媒体查询写死样式(但这样就失去动态切换能力了):

@media (prefers-color-scheme: dark) {
body {
background-color: #121212;
color: #eeeeee;
}
}


但你问题里说“只在页面加载时生效”,说明你可能只写了 CSS 媒体查询,没加 JS 动态监听。所以必须用 JS 去监听变化并更新 DOM 或 CSS 变量。

第四步,补充一个常见问题:iOS Safari 在后台切了暗黑模式,再切回前台才触发事件。也就是说,如果你切了系统主题但没切回浏览器页面,可能要等几秒甚至切换页面后才生效。这是 Safari 的省电策略,不是 bug,但你得知道——它不是不触发,是延迟触发。

你可以加个 visibilitychange 事件兜底,当用户切回页面时强制刷新一次主题判断:

document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
updateTheme(isDark); // 你自己的主题更新函数
}
});


最后,如果你用的是 Vue、React 这类框架,建议把主题监听逻辑封装成一个 hook 或指令,避免在多个组件里重复写。比如 React 里可以这样:

import { useEffect, useState } from 'react';

function useDarkMode() {
const [isDark, setIsDark] = useState(() => {
return window.matchMedia('(prefers-color-scheme: dark)').matches;
});

useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

const handleChange = (e) => setIsDark(e.matches);

if (mediaQuery.addEventListener) {
mediaQuery.addEventListener('change', handleChange);
} else if (mediaQuery.addListener) {
mediaQuery.addListener(handleChange);
}

return () => {
if (mediaQuery.removeEventListener) {
mediaQuery.removeEventListener('change', handleChange);
} else if (mediaQuery.removeListener) {
mediaQuery.removeListener(handleChange);
}
};
}, []);

return isDark;
}


你试试这些步骤,特别是先加 console.log 看事件有没有触发——我见过不少同学以为没触发,其实是浏览器控制台没开,或者切主题时页面没在前台。要是还有问题,把你的具体代码贴出来,我帮你看看是不是 event listener 挂错了地方或者被覆盖了。
点赞 5
2026-02-24 10:01