Preferreds-color-scheme:你好,黑暗,我的老朋友

过度宣传还是必不可少?全面了解深色模式以及如何支持深色模式,为用户提供便利!

简介

深色模式之前的深色模式

绿幕电脑显示器
绿幕(来源

我们对深色模式进行了全面改进。 在个人计算机的黎明时代,深色模式不是选择,而是事实:单色 CRT 计算机显示器的工作原理是向荧光屏幕发射电子束,而早期 CRT 中使用的荧光粉是绿色的。由于文本显示为绿色,而屏幕的其余部分为黑色,因此这些模型通常被称为绿幕

黑底白字文字处理
白底黑字(来源

随后推出的彩色 CRT 通过使用红色、绿色和蓝色磷光体来显示多种颜色。他们通过同时激活这三种荧光粉来产生白光。随着更复杂的 WYSIWYG 桌面出版技术的出现,让虚拟文档看起来像一张实体纸张的想法开始流行起来。

WorldWideWeb 浏览器中的深色调白色网页
WorldWideWeb 浏览器(来源

这也是白底黑字作为设计趋势的起源,此趋势延续到了早期的基于文档的 Web。史上第一款浏览器 WorldWideWeb(请注意,CSS 尚未发明)就是以这种方式显示网页的。趣味知识:有史以来第二款浏览器是行模式浏览器(一种基于终端的浏览器),它在深色模式下显示为绿色。如今,网页和 Web 应用通常采用浅色背景和深色文本的设计,这一基本假设也已在用户代理样式表(包括 Chrome 的样式表)中硬编码。

躺在床上使用智能手机
在床上使用智能手机(来源:Unsplash)

CRT 早已过时。 内容消费和创作已转移到使用背光 LCD 或节能 AMOLED 屏幕的移动设备。体积更小、更便于携带的计算机、平板电脑和智能手机带来了新的使用模式。 上网浏览、娱乐性编程和高端游戏等休闲活动通常在下班后在昏暗的环境中进行。人们甚至会在夜间躺在床上使用设备。 人们在黑暗环境中使用设备的次数越多,回归深色背景浅色文本的想法就越受欢迎。

为何使用深色模式

出于美观原因而采用深色模式

当用户被问及为何喜欢或想要使用深色模式时,最常见的回答是“看起来更舒服”,其次是“优雅而美观”。Apple 在其深色模式开发者文档中明确指出:“对于大多数用户来说,选择启用浅色还是深色外观是一种美学选择,可能与环境光照条件无关。”

Mac OS System 7 中的 CloseView,其中包含
System 7 CloseView(来源

将深色模式用作无障碍工具

还有一些用户实际上需要使用深色模式,并将其用作另一种无障碍功能工具,例如弱视用户。我能找到的这种无障碍工具最早出现在 System 7CloseView 功能中,其中包含用于切换白底黑字黑底白字的开关。虽然 System 7 支持彩色,但默认界面仍为黑白。

在引入颜色后,这些基于反转的实现就暴露了其弱点。Szpiro 低视力人士如何使用计算设备开展的用户研究表明,受访的所有用户都不喜欢反色图片,但许多用户更喜欢在深色背景上使用浅色文本。Apple 提供了一项名为智能反转的功能来满足此用户偏好设置,该功能会反转显示屏上的颜色,但图片、媒体和使用深色样式的某些应用除外。

计算机视觉综合症(也称为数字眼疲劳)是一种特殊的弱视形式,定义“与使用计算机(包括桌面设备、笔记本电脑和平板电脑)和其他电子显示屏(例如智能手机和电子阅读设备)相关的眼部和视力问题的组合。” 有研究提出,青少年使用电子设备(尤其是在夜间)会增加睡眠时间缩短、入睡延迟时间延长和睡眠不足的风险。此外,据Rosenfield 的研究,蓝光的暴露已被广泛报道,与调节昼夜节律和睡眠周期有关,并且不规律的光照环境可能会导致睡眠不足,进而可能影响情绪和任务表现。不规律的光照环境可能会导致睡眠不足,进而可能影响情绪和任务表现。为了限制这些负面影响,您可以通过 iOS 的夜间模式或 Android 的夜光等功能调整显示屏色温来减少蓝光,也可以通过深色主题或深色模式来避免强光或不规则光线。

在 AMOLED 屏幕上使用深色模式可节省电量

最后,众所周知,深色模式可在 AMOLED 屏幕上大量节省电量。专注于 YouTube 等热门 Google 应用的 Android 案例研究表明,节省的电量最高可达 60%。下面的视频详细介绍了这些案例研究以及每款应用可节省的电量。

在操作系统中启用深色模式

现在,我已经介绍了深色模式对许多用户来说如此重要的原因,接下来我们来看看如何支持深色模式。

Android Q 深色模式设置
Android Q 深色主题设置

支持深色模式或深色主题的操作系统通常在设置的某个位置提供了启用深色模式或深色主题的选项。在 macOS X 中,该部分位于系统偏好设置的常规部分,名为外观屏幕截图);在 Windows 10 中,该部分位于颜色部分,名为选择您的颜色屏幕截图)。 对于 Android Q,您可以在显示下找到深色主题切换开关(屏幕截图);在 iOS 13 中,您可以在设置的显示和亮度部分更改外观屏幕截图)。

prefers-color-scheme 媒体查询

在开始之前,先讲一点理论知识。 借助媒体查询,作者可以独立于要呈现的文档来测试和查询用户代理或显示设备的值或功能。它们在 CSS @media 规则中用于有条件地将样式应用于文档,以及在各种其他上下文和语言(例如 HTML 和 JavaScript)中使用。媒体查询级别 5 引入了所谓的用户偏好媒体功能,即网站检测用户首选内容显示方式的方式。

prefers-color-scheme 媒体功能用于检测用户是否请求网页使用浅色或深色主题。它适用于以下值:

  • light:表示用户已通知系统,他们更喜欢使用浅色主题(浅色背景上的深色文本)的页面。
  • dark:表示用户已通知系统,他们更喜欢使用深色主题(浅色文本在深色背景上)的网页。

支持深色模式

了解浏览器是否支持深色模式

由于深色模式是通过媒体查询报告的,因此您可以通过检查媒体查询 prefers-color-scheme 是否完全匹配,轻松检查当前浏览器是否支持深色模式。请注意,我没有添加任何值,而是仅检查媒体查询本身是否匹配。

if (window.matchMedia('(prefers-color-scheme)').media !== 'not all') {
  console.log('🎉 Dark mode is supported');
}

在撰写本文时,Chrome 和 Edge(从版本 76 开始)、Firefox(从版本 67 开始),以及 macOS 上的 Safari(从版本 12.1 开始)和 iOS 上的 Safari(从版本 13 开始)在桌面版和移动版(如适用)均支持 prefers-color-scheme。对于所有其他浏览器,您可以查看我可以使用支持表格吗

在请求时了解用户的偏好设置

借助 Sec-CH-Prefers-Color-Scheme 客户端提示标头,网站可以选择在请求时获取用户的配色方案偏好设置,从而允许服务器内嵌正确的 CSS,从而避免闪烁不正确的配色主题。

深色模式实践

最后,我们来看看支持深色模式在实践中的效果。 就像 Highlander 一样,深色模式只能是其中一种:深色或浅色,绝不能同时使用这两种模式!为什么我要提及这一点?因为这一事实应该会对加载策略产生影响。请勿强制用户在关键渲染路径中下载适用于他们当前不使用的模式的 CSS。 因此,为了优化加载速度,我将示例应用的 CSS 拆分为三个部分,以便推迟非关键 CSS,从而实际显示以下建议:

  • style.css,其中包含在网站上普遍使用的通用规则。
  • 仅包含深色模式所需规则的 dark.css
  • 仅包含浅色模式所需规则的 light.css

加载策略

后两个(light.cssdark.css)是通过 <link media> 查询有条件地加载的。最初,并非所有浏览器都支持 prefers-color-scheme(可使用上文中的模式进行检测),我通过在极小的内嵌脚本中通过条件插入的 <link rel="stylesheet"> 元素加载默认 light.css 文件来动态处理此问题(浅色是任意选择,我也可以将深色设为默认后备体验)。为了避免未设置样式的页面内容闪烁,我会在 light.css 加载完毕之前隐藏页面内容。

<script>
  // If `prefers-color-scheme` is not supported, fall back to light mode.
  // In this case, light.css will be downloaded with `highest` priority.
  if (window.matchMedia('(prefers-color-scheme: dark)').media === 'not all') {
    document.documentElement.style.display = 'none';
    document.head.insertAdjacentHTML(
      'beforeend',
      '<link rel="stylesheet" href="/light.css" onload="document.documentElement.style.display = \'\'">',
    );
  }
</script>
<!--
  Conditionally either load the light or the dark stylesheet. The matching file
  will be downloaded with `highest`, the non-matching file with `lowest`
  priority. If the browser doesn't support `prefers-color-scheme`, the media
  query is unknown and the files are downloaded with `lowest` priority (but
  above I already force `highest` priority for my default light experience).
-->
<link rel="stylesheet" href="/dark.css" media="(prefers-color-scheme: dark)" />
<link
  rel="stylesheet"
  href="/light.css"
  media="(prefers-color-scheme: light)"
/>
<!-- The main stylesheet -->
<link rel="stylesheet" href="/style.css" />

样式表架构

我会尽可能使用 CSS 变量,这样我的通用 style.css 就能够真正做到通用,所有浅色或深色模式自定义都将在另外两个文件 dark.csslight.css 中进行。您可以在下方查看实际样式的摘要,但这足以传达整体概念。 我声明了两个变量 -⁠-⁠color-⁠-⁠background-color,它们实际上会创建浅色底色/深色文本深色底色/浅色文本基准主题。

/* light.css: 👉 dark-on-light */
:root {
  --color: rgb(5, 5, 5);
  --background-color: rgb(250, 250, 250);
}
/* dark.css: 👉 light-on-dark */
:root {
  --color: rgb(250, 250, 250);
  --background-color: rgb(5, 5, 5);
}

然后,在 style.css 中,我在 body { … } 规则中使用这些变量。 由于这些变量是在 :root CSS 伪类(在 HTML 中表示 <html> 元素的选择器,与选择器 html 完全相同,但特异性更高)上定义的,因此它们会向下级联,这对我来说非常有用,因为我可以通过这种方式声明全局 CSS 变量。

/* style.css */
:root {
  color-scheme: light dark;
}

body {
  color: var(--color);
  background-color: var(--background-color);
}

在上面的代码示例中,您可能已经注意到一个值为 light dark 且以空格分隔的属性 color-scheme

这会告知浏览器我的应用支持哪些配色主题,并允许它激活用户代理样式的特殊变体,这对于让浏览器以深色背景和浅色文本渲染表单字段、调整滚动条或启用主题感知突出显示颜色等用途非常有用。color-scheme 的确切详细信息在 CSS 颜色调整模块级别 1 中指定。

然后,只需为网站上的重要内容定义 CSS 变量,即可完成所有其他操作。在使用深色模式时,按语义组织样式会非常有帮助。例如,请考虑调用变量 -⁠-⁠accent-color,而不是 -⁠-⁠highlight-yellow,因为“黄色”在深色模式下可能实际上不是黄色,反之亦然。以下是我在示例中使用的其他一些变量的示例。

/* dark.css */
:root {
  --color: rgb(250, 250, 250);
  --background-color: rgb(5, 5, 5);
  --link-color: rgb(0, 188, 212);
  --main-headline-color: rgb(233, 30, 99);
  --accent-background-color: rgb(0, 188, 212);
  --accent-color: rgb(5, 5, 5);
}
/* light.css */
:root {
  --color: rgb(5, 5, 5);
  --background-color: rgb(250, 250, 250);
  --link-color: rgb(0, 0, 238);
  --main-headline-color: rgb(0, 0, 192);
  --accent-background-color: rgb(0, 0, 238);
  --accent-color: rgb(250, 250, 250);
}

完整示例

在下面嵌入的 Glitch 中,您可以看到将上述概念付诸实践的完整示例。尝试在特定操作系统的设置中切换深色模式,然后观察页面的响应方式。

加载影响

在玩弄此示例时,您可以了解我为什么通过媒体查询加载 dark.csslight.css。请尝试切换深色模式并重新加载页面:目前不匹配的特定样式表仍会加载,但优先级最低,因此它们绝不会与网站当前所需的资源竞争。

网络加载图,显示在浅色模式下,深色模式 CSS 是如何以最低优先级加载的
在浅色模式下,网站会以最低优先级加载深色模式 CSS。
网络加载图,显示在深色模式下,浅色模式 CSS 的加载优先级如何为最低
在深色模式下,网站会以最低优先级加载浅色模式 CSS。
网络加载图,显示在默认的浅色模式下,深色模式 CSS 的加载优先级如何为最低
在不支持 prefers-color-scheme 的浏览器中,网站会以默认的浅色模式显示,并以最低优先级加载深色模式 CSS。

对深色模式更改做出响应

与任何其他媒体查询更改一样,您可以通过 JavaScript 订阅深色模式更改。例如,您可以使用此方法动态更改网页的favicon,或更改用于确定 Chrome 中网址栏颜色的 <meta name="theme-color">。上面的完整示例演示了这一点,如需查看主题颜色和网站图标的更改,请在单独的标签页中打开演示

const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
darkModeMediaQuery.addEventListener('change', (e) => {
  const darkModeOn = e.matches;
  console.log(`Dark mode is ${darkModeOn ? '🌒 on' : '☀️ off'}.`);
});

从 Chromium 93 和 Safari 15 开始,您可以使用 meta 主题颜色元素的 media 属性,根据媒体查询调整颜色。系统会选择第一个匹配的地址。例如,您可以为浅色模式设置一种颜色,为深色模式设置另一种颜色。在撰写本文时,您无法在清单中定义这些属性。请参阅 w3c/manifest#975 GitHub 问题

<meta
  name="theme-color"
  media="(prefers-color-scheme: light)"
  content="white"
/>
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="black" />

调试和测试深色模式

在 DevTools 中模拟 prefers-color-scheme

切换整个操作系统的配色方案很快就会让人感到厌烦,因此 Chrome 开发者工具现在允许您以只影响当前可见标签页的方式模拟用户的首选配色方案。打开命令菜单,开始输入 Rendering,运行 Show Rendering 命令,然后更改 Emulate CSS media feature prefers-color-scheme 选项。

Chrome DevTools 的“Rendering”(渲染)标签页中显示的“Emulate CSS media feature prefers-color-scheme”(模拟 CSS 媒体功能 prefers-color-scheme)选项的屏幕截图

使用 Puppeteer 截取 prefers-color-scheme 的屏幕截图

Puppeteer 是一个 Node.js 库,用于提供高级 API,以便通过 DevTools 协议控制 Chrome 或 Chromium。借助 dark-mode-screenshot,我们提供了一个 Puppeteer 脚本,可让您在深色模式和浅色模式下创建网页的屏幕截图。您可以一次性运行此脚本,也可以将其作为持续集成 (CI) 测试套件的一部分运行。

npx dark-mode-screenshot --url https://googlechromelabs.github.io/dark-mode-toggle/demo/ --output screenshot --fullPage --pause 750

深色模式最佳实践

避免使用纯白色

您可能已经注意到一个小细节,那就是我没有使用纯白色。相反,为了防止白色在周围深色内容的衬托下发光和渗出,我选择了略深一些的白色。rgb(250, 250, 250) 之类的名称就很不错。

为照片重新上色和使其变暗

如果您比较下面的两个屏幕截图,会发现核心主题不仅从浅色底色深色文本更改为深色底色浅色文本,主打图片也略有不同。我的用户调研结果表明,大多数受访者更喜欢在深色模式下使用色彩略不那么鲜艳的图片。我将其称为重新着色

主打图片在深色模式下略微变暗。
主打图片在深色模式下略微变暗。
浅色模式下的常规主打图片。
浅色模式下的常规主打图片。

我可以通过对图片应用 CSS 滤镜来重新着色。 我使用一个 CSS 选择器来匹配网址中不含 .svg 的所有图片,其想法是,我可以对矢量图形(图标)采用与图片(照片)不同的重新着色处理方式,详情请参阅下一部分。请注意,我再次使用了 CSS 变量,以便日后灵活更改过滤条件。

由于仅在深色模式下(即 dark.css 处于活动状态时)需要重新着色,因此 light.css 中没有相应的规则。

/* dark.css */
--image-filter: grayscale(50%);

img:not([src*='.svg']) {
  filter: var(--image-filter);
}

使用 JavaScript 自定义深色模式重新着色强度

每个人的需求不同,对深色模式的需求也不尽相同。通过坚持使用上述重新着色方法,我可以轻松将灰度强度设为可通过 JavaScript 更改的用户偏好设置,并且通过设置值为 0%,我还可以完全停用重新着色。请注意,document.documentElement 提供了对文档根元素的引用,也就是我可以使用 :root CSS 伪类引用的同一元素。

const filter = 'grayscale(70%)';
document.documentElement.style.setProperty('--image-filter', value);

反转矢量图形和图标

对于矢量图形(在本例中,它们用作通过 <img> 元素引用的图标),我使用了不同的重新着色方法。虽然研究表明用户不喜欢将照片反转,但对于大多数图标,反转效果非常好。同样,我使用 CSS 变量来确定常规状态和 :hover 状态下的反转量。

图标在深色模式下会反转。
图标在深色模式下会反转。
浅色模式下的常规图标。
浅色模式下的常规图标。

请再次注意,我只在 dark.css 中反转图标,而不在 light.css 中反转图标;以及 :hover 如何在这两种情况下获得不同的反转强度,以使图标看起来略暗或略亮,具体取决于用户选择的模式。

/* dark.css */
--icon-filter: invert(100%);
--icon-filter_hover: invert(40%);

img[src*='.svg'] {
  filter: var(--icon-filter);
}
/* light.css */
--icon-filter_hover: invert(60%);
/* style.css */
img[src*='.svg']:hover {
  filter: var(--icon-filter_hover);
}

对内嵌 SVG 使用 currentColor

对于内嵌 SVG 图片,您可以使用表示元素 color 属性值的 currentColor CSS 关键字,而不是使用反转滤镜。这样,您就可以在默认情况下不接收 color 值的属性上使用 color 值。为方便起见,如果 currentColor 用作 SVG fillstroke 属性的值,则它会改为从颜色属性的继承值中获取值。更好的是:这也适用于 <svg><use href="…"></svg>,因此您可以使用单独的资源,而 currentColor 仍会在上下文中应用。请注意,这仅适用于内嵌 SVG 或 <use href="…"> SVG,而不适用于作为图片的 src 或通过 CSS 以某种方式引用的 SVG。您可以在下面的演示中看到此应用。

<!-- Some inline SVG -->
<svg xmlns="http://www.w3.org/2000/svg"
    stroke="currentColor"
>
  […]
</svg>

模式之间的流畅转换

由于 colorbackground-color 都是可呈现动画效果的 CSS 属性,因此从深色模式切换到浅色模式或反之,可以实现平滑切换。创建动画只需为这两个属性声明两个 transition 即可。以下示例展示了整体思路,您可以在演示中亲身体验。

body {
  --duration: 0.5s;
  --timing: ease;

  color: var(--color);
  background-color: var(--background-color);

  transition: color var(--duration) var(--timing), background-color var(
        --duration
      ) var(--timing);
}

深色模式下的艺术指导

虽然出于加载性能方面的原因,我通常建议您仅在 <link> 元素的 media 属性中使用 prefers-color-scheme(而不是在样式表中内嵌),但在某些情况下,您实际上可能希望直接在 HTML 代码中内嵌 prefers-color-scheme。艺术指导就是这样一种情况。 在 Web 上,艺术指导涉及网页的整体视觉外观,以及网页如何通过视觉传达信息、激发情绪、对比特征,并从心理上吸引目标受众群体。

对于深色模式,具体取决于设计师的判断,决定在特定模式下哪张图片最合适,以及重新着色图片是否够好。如果与 <picture> 元素搭配使用,则可使要显示的图片的 <source> 依赖于 media 属性。在以下示例中,我会显示深色模式下的西半球,以及浅色模式下的东半球,或者在未指定偏好设置的情况下,在所有其他情况下均默认显示东半球。当然,这只是为了说明问题。在设备上切换深色模式,看看效果。

<picture>
  <source srcset="western.webp" media="(prefers-color-scheme: dark)" />
  <source srcset="eastern.webp" media="(prefers-color-scheme: light)" />
  <img src="eastern.webp" />
</picture>

深色模式,但添加了停用选项

如上文为何采用深色模式部分所述,深色模式对大多数用户来说都是美观的选择。因此,有些用户可能实际上更喜欢将操作系统界面设为深色,但仍希望以习惯的方式查看网页。一个很好的做法是,最初遵循浏览器通过 prefers-color-scheme 发送的信号,但随后可选择允许用户替换其系统级设置。

<dark-mode-toggle> 自定义元素

当然,您可以自行创建此类代码,但也可以直接使用我为此目的专门创建的现成自定义元素(Web 组件)。此类 widget 称为 <dark-mode-toggle>,可向您的页面添加可完全自定义的切换开关(深色模式:开启/关闭)或主题切换器(主题:浅色/深色)。以下演示展示了该元素的实际运用(哦,我还在其他 示例 上方悄悄地将其添加了进去)。

<dark-mode-toggle
  legend="Theme Switcher"
  appearance="switch"
  dark="Dark"
  light="Light"
  remember="Remember this"
></dark-mode-toggle>
浅色模式下的 dark-mode-toggle。
浅色模式下的 <dark-mode-toggle>
浅色模式下的 dark-mode-toggle。
<dark-mode-toggle> 在深色模式下。

请尝试点击或点按以下演示版中右上角的深色模式控件。 如果您选中第三个和第四个控件中的复选框,请查看系统如何记住您选择的模式,即使您重新加载页面也是如此。这样,访问者就可以让操作系统保持深色模式,但在光亮模式下浏览您的网站,反之亦然。

总结

使用和支持深色模式很有趣,并且可以开辟新的设计途径。 对于部分访问者来说,这可能会决定他们能否顺利使用您的网站,以及能否成为满意的用户。虽然存在一些陷阱,并且您肯定需要进行仔细测试,但深色模式绝对是您展示对所有用户的关心的绝佳机会。本文中提到的最佳实践以及 <dark-mode-toggle> 自定义元素等实用工具,应该能让您有信心打造出出色的深色模式体验。欢迎在 Twitter 上告诉我您创作的内容,以及这篇文章是否对您有所帮助,或者您是否有改进建议。感谢阅读!🌒?

有关 prefers-color-scheme 媒体查询的资源:

有关 color-scheme 元标记和 CSS 属性的资源:

常规深色模式链接:

本文的背景研究文章:

致谢

prefers-color-scheme 媒体功能、color-scheme CSS 属性和相关元标记由 👏? Rune Lillesveen 实现。Rune 还是 CSS 颜色调整模块级别 1 规范的共同编辑者。在此,我衷心感谢 Lukasz ZbylutRowan MerewoodChirag DesaiRob Dodson 对本文的仔细审核。 加载策略Jake Archibald 的杰作。 Emilio Cobos Álvarez 向我指出了正确的 prefers-color-scheme 检测方法。关于引用的 SVG 和 currentColor 的提示来自 Timothy Hatcher。最后,感谢参与各种用户研究的众多匿名参与者,他们为本文中的建议提供了有益帮助。主打图片由 Nathan Anderson 提供。