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,返回 MediaQueryList
  • media.matches:当前文档是否匹配该 query(boolean
  • media.media:传入的 query 字符串

监听系统主题变化

用户切换系统外观时,不必轮询,监听 MediaQueryListchange 即可。事件对象是 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 模式)。

相关链接

评论

0 条讨论

后即可评论与回复。

还没有评论,来抢沙发吧。