浏览器深色模式
2024-9-29
| 2024-12-12
字数 2533阅读时长 7 分钟
beizhu
type
Post
status
Published
date
Sep 29, 2024
slug
summary
深色模式是一种主题,支持通过CSS的prefers-color-scheme媒体查询实现,能够在不同环境下提供更好的用户体验。尽管浏览器支持率高,但兼容性仍然是个问题,许多网站使用手动开关来切换模式。结合手动开关和媒体查询可以提升用户体验。提供了多种实现深色模式的方法,包括CSS变量和条件加载CSS文件。
tags
CSS
React
category
技术
icon
password
随着越来越多的操作系统、浏览器开始支持 Dark Mode、支持相应的 Media Queries Level 5,越来越多的网站开始添加深色模式。

什么是「深色模式」

很多操作系统在日落后会自动切换到「深色模式」、并不意味着「深色模式」就是「夜间模式」。「夜间模式」用于夜晚的弱光环境,主要目的是保护眼睛、减少强光刺激、避免影响睡眠,不难理解为什么 macOS 的 Night Shift 会自动调节屏幕色温、Android(AOSP)到了夜间可以选择启用系统级「琥珀色」滤镜。
 
notion image
「深色模式」更像是一个主题,即使在白天也可以使用。不论是为了在 OLED 屏幕上省电、亦或是减少白光刺激护眼、亦或是暗色模式对色盲用户更加友好,总之 macOS 率先提出了系统级的「暗色模式」、并在 WebKit 中增加了对应的 Media Query,而后 Chromium、Firefox 先后跟进,如今兼容 prefers-color-scheme 的浏览器占有率已经高达 81.82%。
CSS 媒体查询 @media 是一个足够强大的特性,可以有条件地将样式应用于文档和各种上下文中。Media Queries Level 5 草案 中提出了深色模式的判断方式 prefers-color-scheme,包含 lightdarkno-preference 三种值。而不支持 Media Queries 5 的浏览器会直接无视 CSS 中的 prefers-color-scheme Media Query,无需额外的代码即可优雅降级。
还记得我刚刚说过「深色模式更像一个主题」么?为网站新增深色模式就如同换肤功能;搭配 prefers-color-scheme,编写深色模式的思路就如同编写响应式一般、无需赘述,结合几段 Code Snippet 一笔带过:

CSS Variable 的方法实现深色模式

通过维护两套 CSS Variable,可以快速切换不同的配色方案。这种方法特点是所需代码较少,缺点是 CSS Variable 的兼容性较差,可能还需要引入额外的 Polyfill。

为深色模式单独编写样式

直接维护两套样式的方法清晰直观、任何网站都可以基于这种方法进行改造。但会造成冗余代码、较难实现统一的风格、后期不易维护。

条件性加载深色模式的 CSS 文件

利用 <link> 标签的 Media Query,甚至可以单独加载暗色模式的 CSS 文件。
需要注意 CSS 选择器的权重,因此作为可选的 dark.css 一定要放在 main.css 之后加载。
除了上述三种方式以外,使用 CSS filter 或 mix-blend-mode 还可以实现对网站整体色调的改变,可以确保配色风格的统一性。

「深色模式」的兼容性

虽然有了优雅的 prefers-color-scheme 可以识别操作系统的显示模式,但是对于用户来说,仅依赖 Media Query 的「深色模式」并不能带来很好的体验。
首先是浏览器兼容性。虽然支持该特性的浏览器的市场占有率非常喜人,但是从版本号上来看却并不乐观:
notion image
考虑到使用 Chormium 70 内核甚至 Tencent X5 内核的国产浏览器,大部分用户并没有机会体验到深色模式。除此以外,操作系统级别的「深色模式」实现也会受到 OEM 厂商的影响 —— 虽然 Android 10(AOSP)提供「深色模式」,但是一加的 OxygenOS 却将其深藏在系统主题设置里,没有自动切换、在 Quick Settings 里也没有快速的切换开关。

设计一个用户友好的「深色模式」

受限于兼容性和复杂的操作系统,大部分网站依然在使用更传统的「开关」切换 —— 通过 toggle <html> 或
<body> 的 class 属性实现在两套样式之间切换、并将开关的状态记忆在 localStorage 中的方法虽然有效,却是无奈之举,手动切换开关相比 prefers-color-scheme 也不够优雅。如果将「开关」和 prefers-color-scheme 结合起来,就可以带来更好的用户体验:
  • 对于不兼容的浏览器或操作系统,访客依然可以通过开关手动切换显示模式
  • 对于兼容的浏览器或操作系统,Media Query 能够实现在两种显示模式之间切换
  • 在兼容的浏览器或操作系统上,用户还可以通过开关 override 当前的显示模式
在将两者组合在一起时,不能简单地用「开关」覆盖 prefers-color-scheme,否则用户触发开关、状态被永久记忆在 localStorage 之后,就变成了僵硬的手动模式。
举个例子。访客可能在操作系统还没有自动切换到「深色模式」时通过网站上的开关切换显示模式,经过一个夜晚后到了次日白天、访客再度访问网站时,自然希望不需要再切换开关、网站就能以常规的浅色模式显示。因此设计思路是当 prefers-color-scheme 的值发生改变(从 与用户需要的显示模式不同 变成 相同)时清空 localStorage 中储存的开关状态,此时显示模式切换回基于 Media Query 的「自动」模式。
Talk is cheap, here goes the code.
首先是 CSS:
真是令人看的头大,让我们逐行来看都是些什么:
  • 在 :root 下定义了一个 CSS Variable -color-mode: light 和在浅色模式下用到的 CSS Variable(比如使用深色 #333 作为主要字体颜色)。
  • 使用 prefers-color-scheme 的 Media Query 定义深色模式下的 CSS Variable: -color-mode: light 。深色模式的样式(如浅色 #eff 作为主要字体颜色)要定义在 :not([data-user-color-scheme]) 伪类下以避免「开关」的行为覆盖浏览器的样式。
  • 为 [data-user-color-scheme='dark'] 再定义一遍深色模式下用到的样式。
有了这段 CSS,不难理解深色模式何时会生效:当操作系统使用「深色模式」且 <html> 或 <body> 标签上没有 data-user-color-scheme 属性时、或者存在 data-user-color-scheme 属性且值为 dark 时。
然后是困难的部分了:编写 JavaScript 为「开关」添加行为。
先定义一些常量:
接下来,用 try {} catch (e) {} 封装一下 localStorage 的操作,以应对 HTML5 Storage 被禁用、localStorage 被写满、localStorage 实现不完整的情况:
我们还需要一个函数读取当前 prefers-color-scheme 的方法。由于已经在 CSS 中定义了 --color-mode,所以在 JS 中直接读取就好了:
还记得我们需要自动取消手动模式回到 prefers-color-scheme 么?意味着我们需要一个函数清掉 LS、删掉 <html> 存在的 data-user-color-scheme 属性:
接下来是起主要作用的函数了,负责为 <html> 标签修改 data-user-color-scheme 属性:
当然,「开关」还需要一个函数,这个函数负责获取相反的显示模式,同时还要将新的模式写入 localStorage 存储起来:
相关的函数都定义完了,是时候添加函数执行了:
 
 
 
FROM
 
  • CSS
  • React
  • 技术管理者的核心能力是什么JS new Intl.NumberFormat
    Loading...