Dark Mode 最佳实践 ᵃˡᵐᵒˢᵗ

552 阅读1分钟

不考虑滤镜等奇技淫巧。

样式分离切换

  1. <html class="dark"> 通过类名更改切换。tailwindCSS、windiCSS 使用的方法。掘金插件是放在 <body> 。 可以结合 CSS 变量使用。
body {
  color: var(--text-color);
  --text-color: black;
}
.dark body {
  --text-color: white;
}
  1. 分别写两套样式,通过 <link herf="dark.css"> 切换。
  2. 如果只需要跟随系统,用 CSS 媒体查询即可:
@media (perfers-color-scheme: dark) {}

用户偏好 > 系统偏好

把用户偏好分为三类以上:

  • 跟随系统,OS
  • 夜间,Dark
  • 日间,Light
  • 跟随日月升落,Time(少见需求,忽略)

也可以使用 js, 结果是一个 MediaQueryList 对象:

window.matchMedia('(prefers-color-scheme: dark)')
// {media: '(prefers-color-scheme: dark)', matches: true, onchange: null}

下面介绍一种渐进式(优先满足用户偏好,用户没有偏好时跟随系统)的设计:

type ColorScheme = 'OS' | 'Dark' | 'Light'

const colorScheme: ColorScheme = localStorage.getItem('colorScheme') || 'OS'

const setColorScheme = (scheme: ColorScheme) => {
    locolStorage.setItem('colorScheme', scheme)
    switch (scheme) {
        case 'OS':
            // 这样写其实是优先 Light
            if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
                document.documentElement.classList.add('dark')
            } else {
                document.documentElement.classList.remove('dark')
            }
            // 监听系统色彩主题变化
            window.matchMedia('(prefers-color-scheme: dark)').onchange = () => {
                document.documentElement.classList.toggle('dark')
            }
            break;
        case 'Dark':
            document.documentElement.classList.add('dark')
            // 移除监听
            window.matchMedia('(prefers-color-scheme: dark)').onchange = null
            break;
        case 'Light':
            document.documentElement.classList.remove('dark')
            window.matchMedia('(prefers-color-scheme: dark)').onchange = null
            break;
    }
}

setColorScheme(colorScheme)

DarMode.gif

初始化

如果页面渲染时默认主题刚出现就被替换,产生刷新效果,影响用户体验,称 FOIT(flash of incorrect theme)。

为避免 FOIT,可把样式初始化代码写到 <head> 标签里面。

SSR

同构,或可考虑后端存储一下。

但无论如何,用户如果选择“跟随系统”,FOIT 就有 50% 几率出现了。 (没用的避免方法:可以考虑在“跟随系统”的情况下,先加一个额外启动页,把媒体查询的结果发回去再路由到原页面。)