主题加暗色模式:CSS 变量、localStorage 和那个躲不掉的首屏闪白

· zblog · 阅读 14
摘要

暗色模式不难,难的是闪白。 先说结构。别写两套样式表,用 CSS 变量。 在 :root 里定义一套亮色变量,比如 --bg、--text、--border、--card。然后 html.dark 里把这几个变量覆盖成暗色值。所有组件只

暗色模式不难,难的是闪白。

先说结构。别写两套样式表,用 CSS 变量。

在 :root 里定义一套亮色变量,比如 --bg、--text、--border、--card。然后 html.dark 里把这几个变量覆盖成暗色值。所有组件只用变量,不写死颜色。

这套做法的好处是切换瞬时完成,不用重新加载任何资源。改一个 class 就够。

存哪。

localStorage。键名起个带前缀的,比如 fh_theme,值是 dark / light。

还要不要跟随系统?要。用户没手动选过的时候,读 prefers-color-scheme。选过就以他的为准,别再被系统覆盖。

所以逻辑是三层:localStorage 有值 → 用它;没有 → 问系统;都没有 → 亮色。

然后是那个闪白。

问题出在时序。页面开始渲染时,HTML 已经出来了,但外部 js 还没下载完。这时候没有任何 class,用户看到的是亮色。等 js 跑起来加上 dark class,整个页面才翻黑。

一次完整闪烁,肉眼能看出来。慢网更明显。

解法只有一个:在 head 里内联一段脚本,越早越好,早到 css 之前。

这段脚本不长,就是读 localStorage、判断系统偏好、给 documentElement 加 class。它不依赖任何外部文件,几十毫秒就跑完,赶在首次绘制之前。

内联脚本看着不优雅,但这是唯一解。别想着用 css 的 @media (prefers-color-scheme) 兜底——它只认系统设置,用户手动切换的偏好它不知道。

切换按钮。

点击后做三件事:toggle class、写 localStorage、更新按钮图标。

有个细节容易被忘:写 localStorage 可能失败。无痕模式、存储满、用户禁用了,都会抛异常。得包 try-catch,失败就只切当前页,别让整个切换逻辑崩掉。

还有一个坑。

图片。暗色模式下,白色背景的 PNG 会很刺眼。可以给图片容器加一层 filter: brightness(.85),或者干脆检测到暗色时给图片加个淡边框,让它和背景有分界。

我用的后者。虽然没那么彻底,但不会把用户上传的正常照片压暗。

你主题现在有暗色开关吗?还是只跟随系统?