CSS dark mode
2019年3月30日

macOS Mojave(2018)引入了深色模式,深色模式使用一种较深的配色方案,这种配色作用于整个系统,包括 Mac 随附的应用。另外,第三方应用也可以适配这一模式。
当时 macOS Mojave 10.14.4 自带的 Safari 开始支持 CSS 草案中的 prefers-color-scheme media query,这样我们可以使网页以深色模式呈现。下文在此基础上补充了后续更常用的 JS 检测与手动主题切换写法。
CSS:prefers-color-scheme
prefers-color-scheme 用于检测用户是否希望页面使用浅色或深色外观。常见取值:
| 值 | 含义 |
|---|---|
light |
用户偏好浅色 |
dark |
用户偏好深色 |
no-preference |
未明确偏好(部分环境) |
:root {
color-scheme: light dark;
--bg: #ffffff;
--fg: #111111;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #232325;
--fg: rgba(229, 225, 216, 0.8);
}
}
body {
background: var(--bg);
color: var(--fg);
}color-scheme 可提示浏览器为表单控件、滚动条等使用匹配的原生外观;在深色背景下通常建议同时设置。
也可以写浅色分支,与深色对称:
@media (prefers-color-scheme: light) {
body {
background: #ffffff;
color: #111111;
}
}如果不使用 Safari,或者访问不支持深色模式的网站,可以试试 Dark Reader 这款浏览器插件。
JavaScript:用 matchMedia 检测主题
CSS 负责样式,JS 则通过 window.matchMedia() 读取同一套 media query 的结果。它返回 MediaQueryList,可即时判断,也可监听变化。
检测当前是否为深色
const media = window.matchMedia("(prefers-color-scheme: dark)");
if (media.matches) {
console.log("当前是深色模式");
} else {
console.log("当前是浅色模式");
}要点:
matchMedia(query):编译 media query,返回MediaQueryListmedia.matches:当前文档是否匹配该 query(boolean)media.media:传入的 query 字符串
监听系统主题变化
用户切换系统外观时,不必轮询,监听 MediaQueryList 的 change 即可。事件对象是 MediaQueryListEvent,同样带有 matches / media。
const media = window.matchMedia("(prefers-color-scheme: dark)");
function onColorSchemeChange(event) {
// event.matches === true 表示偏好深色
document.documentElement.dataset.theme = event.matches ? "dark" : "light";
}
// 先按当前结果应用一次
onColorSchemeChange(media);
// 推荐:addEventListener("change", ...)
media.addEventListener("change", onColorSchemeChange);
// 卸载时记得移除
// media.removeEventListener("change", onColorSchemeChange);兼容说明:旧环境曾用已废弃的 addListener / removeListener;现代浏览器应使用 addEventListener / removeEventListener。若需兼容很老的 Safari,可做一层回退:
function listen(media, handler) {
if (typeof media.addEventListener === "function") {
media.addEventListener("change", handler);
return () => media.removeEventListener("change", handler);
}
media.addListener(handler);
return () => media.removeListener(handler);
}与手动切换主题配合
站点常提供「浅色 / 深色 / 跟随系统」。跟随系统时用 matchMedia;手动覆盖时用 class 或 data-theme,并用 CSS 变量统一配色:
function applyTheme(mode) {
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
const resolved = mode === "auto" ? (prefersDark ? "dark" : "light") : mode;
const root = document.documentElement;
root.classList.remove("light", "dark");
root.classList.add(resolved);
root.style.colorScheme = resolved;
localStorage.setItem("theme", mode);
}
const media = window.matchMedia("(prefers-color-scheme: dark)");
media.addEventListener("change", () => {
if (localStorage.getItem("theme") === "auto") {
applyTheme("auto");
}
});对应 CSS 可用 class 驱动,而不再只依赖 media query:
html.light {
--bg: #ffffff;
--fg: #111111;
}
html.dark {
--bg: #232325;
--fg: rgba(229, 225, 216, 0.8);
}这样既能跟随系统,也能在页面内手动切换,并在系统主题变化时自动更新(仅 auto 模式)。