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 |
未明确偏好(部分环境) |
1234567891011121314151617: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 可提示浏览器为表单控件、滚动条等使用匹配的原生外观;在深色背景下通常建议同时设置。
也可以写浅色分支,与深色对称:
123456@media (prefers-color-scheme: light) { body { background: #ffffff; color: #111111; }}如果不使用 Safari,或者访问不支持深色模式的网站,可以试试 Dark Reader 这款浏览器插件。
JavaScript:用 matchMedia 检测主题
CSS 负责样式,JS 则通过 window.matchMedia() 读取同一套 media query 的结果。它返回 MediaQueryList,可即时判断,也可监听变化。
检测当前是否为深色
1234567const 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。
123456789101112131415const 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,可做一层回退:
12345678function 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 变量统一配色:
1234567891011121314151617function 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:
123456789html.light { --bg: #ffffff; --fg: #111111;}html.dark { --bg: #232325; --fg: rgba(229, 225, 216, 0.8);}这样既能跟随系统,也能在页面内手动切换,并在系统主题变化时自动更新(仅 auto 模式)。