深色模式开发的最佳实践

深色模式开发的最佳实践

自动深色模式

使用 CSS 媒体查询

相信大家在查阅深色模式实现时,都遇到过这种写法:

css
@media (prefers-color-scheme: dark) {
    body {
        background: black;
        color: white;
    }
}

渐进增强而非并列查询

有些同学会说,诶,我有个好主意,我了解 prefers-color-scheme 的值还有 lightno-preference,我把基础样式并列地写进这三种媒体查询里。

这对吗?Chrome 76 起才支持 prefers-color-scheme 媒体特性,放在某些兼容性差的浏览器里,这些规则都不会生效。

所以最好采用「渐进增强」的策略,即先写基础样式,再写深色模式样式,这样可以保证低版本内核的浏览器正常显示。

css
body {
    background: white;
    color: black;
}

@media (prefers-color-scheme: dark) {
    body {
        background: black;
        color: white;
    }
}

使用 CSS 变量

又有同学要头疼了,按钮、弱文本也要适应深色模式,那我岂不得维护许多深色模式下的 CSS 声明块?

这个时候,CSS 变量 就派上用场了。我们可以维护一个普通模式和深色模式的「调色盘」,其他声明块中需要使用颜色,只需要通过 var(--my-color-1) 的形式引用即可。

另外,要注意各个颜色模式下的对比度,在确保文字清晰的同时,不要使用过高的对比度(纯黑、纯白),让用户的体验更舒适。收起 「A屏黑」 的想法,纯黑色省电的代价是黑暗环境中高对比度对用户视觉的刺激。

手动深色模式

又有同学在想了:我们做出的自动深色模式只是设备支持下的自动切换小彩蛋,如何显式展示技术力,比如放个深色模式按钮呢?

假设我们已经通过 localStorage 存储了用户主题并在加载时给 <html> 添加了 data-theme 属性,看起来完美实现了颜色模式切换,其实还会遇到几个降低用户体验的问题。

浏览器生成的自动深色模式

你的浅色模式在设备的浅色模式下表现很好,但是当设备在深色模式时,你的浅色模式可能表现得一团糊涂。

怎会如此!?Chrome 的贴心设计优化了不支持深色模式的网页显示,但成为了开发者在深色模式下展示浅色网页的心智负担。

我们需要通过 color-scheme 告诉浏览器网页支持深色和浅色模式,不要让浏览器扳倒我们自己的适配。两种方法二选一即可:

  • 在 HTML 头部通过元数据声明 <meta name="color-scheme" content="light dark">
  • 在 CSS 中声明 :root { color-scheme: light dark; }

记得在手动深色模式(类名而非媒体查询的实现)下设置 [data-theme="dark"] { color-scheme: dark; }。不然想一想 color-scheme: light dark 会怎么表现?滚动条、按钮等元素在 用户代理样式表 给出的是浅色的样式!这和手动深色模式的预期不一致。

页面初载时的闪烁

你的深色模式在设备深色模式下表现很好,但是当设备在浅色模式下,你的深色模式在初载时可能闪白,这对于用户眼睛的伤害是巨大的(点名批评 GitHub Docs)。

我曾使用 Pinia 管理用户主题,但 Pinia 的挂载需要时间,产生了页面闪白问题。我留意到一些网站是没有的,比如 VitePress,我研究了其代码实现(vitepress/src/node/config.ts#L263)。它在 <head> 中添加了同步的 <script>,通过 IIFE 立即执行函数,在 DOM 构建完成前就完成了主题切换,避免了页面闪白。

html
<head>
    <script>
        if (localStorage.getItem('theme') === 'dark')
            document.documentElement.setAttribute('data-theme', 'dark')
    </script>
</head>

一致性和 CSS 代码冗余

代码冗余会轻松地创建不一致,而不一致带来潜在的破坏力是惊人的:

这样,你的深色模式定义了两次,你需要小心地维护这些代码。也许可以用 @mixin 来减少冗余?但终归不是完美的解决方案。

这时,抛弃掉 [data-theme="auto"] 就显得有用了——通过在 JS 中提前让「自动」的状态「塌缩」为确定的「浅色」或「深色」,CSS 中只需维护两种模式下的对应样式即可。

不要忘记监听 prefers-color-scheme 的变化:

这里的代码只是给出实现思路,具体实现一个完美的深色模式还要多费些心思。什么,竟然有人操作 document.getElementsByTagName('html')[0].className?回家吧孩子。

Take care of users, and code well. ☝️

前端字体二三事
寻不回手工油糕

评论区

评论加载中...