Frontend / 04

给静态站点加一个轻量的深色模式

不引入框架,只用设计令牌、系统偏好和一段很短的状态逻辑。

深色模式最麻烦的地方不是按钮,而是让第一次访问、系统偏好和用户手动选择三者有清晰的优先级。我的规则是:手动选择最高,未选择时跟随系统。

先把颜色变成语义

不要在深色规则里逐个覆盖几十个十六进制值。先把背景、正文、弱化文字、边框和强调色变成 CSS 变量:

:root {
  color-scheme: light;
  --bg: #fff;
  --ink: #0b0c0e;
  --muted: #60646c;
}

[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0d0f12;
  --ink: #f2f3f5;
  --muted: #a4a9b2;
}

color-scheme 会提示浏览器使用对应的表单控件、滚动条和原生界面配色,减少局部仍然刺眼的情况。

避免加载时闪白

外部脚本通常在页面渲染后执行,因此深色用户会先看到一瞬白屏。解决办法是在 head 中放一小段同步脚本,尽早给根元素写入主题属性:

const saved = localStorage.getItem('theme');
const systemDark = matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.dataset.theme = saved || (systemDark ? 'dark' : 'light');

主脚本随后只负责按钮事件、保存选择和更新无障碍标签。若用户没有手动选择,也可以监听系统主题变化。

颜色不是简单反转

纯黑背景配纯白文字的对比过强,长文阅读容易疲劳。深灰背景和略低亮度的正文更稳定;边框也应单独调整,不能依赖透明黑色在两种背景下都成立。

最后别忘了代码块、焦点轮廓、链接悬停和浏览器主题色。用无痕窗口分别测试首次访问,再测试刷新后的记忆行为,通常就能抓到大多数遗漏。