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

被过度炒作还是必需品?了解关于黑暗模式的一切,以及如何支持它以造福您的用户!

简介

黑暗模式之前的黑暗模式

Green screen computer monitor
绿屏 (来源)

我们已经完全回到了黑暗模式的起点。在个人计算的初期,黑暗模式不是一种选择,而是一个事实:单色 CRT 计算机显示器通过在荧光屏上发射电子束来工作,早期 CRT 中使用的荧光粉是绿色的。由于文本以绿色显示,屏幕的其余部分为黑色,因此这些型号通常被称为 绿屏

Dark-on-white word processing
黑底白字 (来源)

随后推出的彩色 CRT 通过使用红色、绿色和蓝色荧光粉来显示多种颜色。它们通过同时激活所有三种荧光粉来创建白色。随着更复杂的 WYSIWYG 桌面出版 的出现,使虚拟文档类似于物理纸张的想法开始流行起来。

Dark-on-white webpage in the WorldWideWeb browser
WorldWideWeb 浏览器 (来源)

这就是 黑底白字 作为一种设计趋势的开始,并且这种趋势被延续到了 早期的基于文档的 Web。有史以来的第一个浏览器 WorldWideWeb(记住,CSS 甚至还没有发明),以这种方式显示网页。有趣的事实:有史以来的第二个浏览器 Line Mode Browser—一个基于终端的浏览器—是深色背景上的绿色文字。如今,网页和 Web 应用程序通常以浅色背景上的深色文本进行设计,这是一个基线假设,也硬编码在用户代理样式表中,包括 Chrome 的

Smartphone used while lying in bed
在床上使用智能手机 (来源: Unsplash)

CRT 的时代早已过去。内容消费和创作已经转移到使用背光 LCD 或节能 AMOLED 屏幕的移动设备。更小巧、更便携的计算机、平板电脑和智能手机带来了新的使用模式。网页浏览、趣味编程和高端游戏等休闲任务经常在夜间昏暗的环境中进行。人们甚至在夜间在床上享受他们的设备。越来越多的人在黑暗中使用他们的设备,浅色背景上的深色文字 的回归想法变得越来越流行。

为什么选择黑暗模式

出于审美原因的黑暗模式

当人们被问到 为什么他们喜欢或想要黑暗模式 时,最流行的回答是 “它对眼睛更友好”,其次是 “它优雅而美观”。Apple 在他们的 黑暗模式开发者文档 中明确写道:“对于大多数用户来说,选择启用浅色外观还是深色外观是一种审美选择,可能与环境照明条件无关。”

CloseView in Mac OS System 7 with
System 7 CloseView (来源)

作为辅助工具的黑暗模式

还有一些人实际上 需要 黑暗模式,并将其用作另一种辅助工具,例如,视力低下的用户。我能找到的此类辅助工具的最早出现是 System 7CloseView 功能,它具有 黑底白字白底黑字 的切换开关。虽然 System 7 支持彩色,但默认的用户界面仍然是黑白的。

这些基于反转的实现方式在引入颜色后显示出其弱点。Szpiro 等人视力低下的人如何访问计算设备 的用户研究表明,所有受访用户都不喜欢反转图像,但许多人更喜欢深色背景上的浅色文本。Apple 通过一项名为 智能反转 的功能来满足用户的这种偏好,该功能反转显示器上的颜色,但图像、媒体和一些使用深色样式的应用除外。

一种特殊的视力低下形式是计算机视觉综合征,也称为数字眼疲劳,它被 定义“与使用计算机(包括台式机、笔记本电脑和平板电脑)和其他电子显示器(例如智能手机和电子阅读设备)相关的眼睛和视力问题的组合。” 有人 提出,青少年,尤其是在夜间使用电子设备,会导致睡眠时间缩短、入睡潜伏期延长和睡眠不足的风险增加。此外,蓝光照射已被广泛 报道 参与 昼夜节律 和睡眠周期的调节,不规则的光线环境可能会导致睡眠剥夺,可能影响情绪和任务表现,根据 Rosenfield 的研究。为了限制这些负面影响,通过调整显示颜色温度来减少蓝光,例如 iOS 的 Night Shift 或 Android 的 Night Light 等功能可以有所帮助,以及通过深色主题或黑暗模式来避免强光或一般不规则的光线。

AMOLED 屏幕上的黑暗模式省电功能

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

在操作系统中激活黑暗模式

既然我已经介绍了为什么黑暗模式对许多用户来说如此重要,那么让我们回顾一下如何支持它。

Android Q dark mode settings
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 上的 13 及更高版本在桌面和移动设备上(如果可用)均支持 prefers-color-scheme。对于所有其他浏览器,您可以查看 Can I use 支持表

在请求时了解用户的偏好

Sec-CH-Prefers-Color-Scheme 客户端提示标头允许站点在请求时选择性地获取用户的配色方案偏好,从而允许服务器内联正确的 CSS,从而避免错误颜色主题的闪烁。

黑暗模式实践

最后让我们看看在实践中支持黑暗模式是什么样的。就像 Highlander 一样,对于黑暗模式 只能有一个:黑暗或光明,但绝不能两者兼有!我为什么要提到这一点?因为这个事实应该对加载策略产生影响。请不要强迫用户在关键渲染路径中下载他们当前不使用的模式的 CSS。 为了优化加载速度,我因此将我的示例应用程序的 CSS 分成了三个部分,该示例应用程序在实践中显示以下建议,以便 延迟非关键 CSS

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

加载策略

后两者,light.cssdark.css,使用 <link media> 查询有条件地加载。最初,并非所有浏览器都支持 prefers-color-scheme(可以使用 上面的模式 检测到),我通过动态加载默认的 light.css 文件来处理这个问题,方法是在微小的内联脚本中条件性地插入 <link rel="stylesheet"> 元素(浅色是任意选择,我也可以将深色作为默认回退体验)。为了避免 未样式内容闪烁,我在 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);
}

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

这告诉浏览器我的应用支持哪些颜色主题,并允许它激活用户代理样式表的特殊变体,这对于例如让浏览器使用深色背景和浅色文本呈现表单字段、调整滚动条或启用主题感知突出显示颜色很有用。color-scheme 的确切细节在 CSS 颜色调整模块级别 1 中指定。

剩下的就只是为我网站上重要的事物定义 CSS 变量了。在处理黑暗模式时,语义化地组织样式非常有帮助。例如,与其使用 -⁠-⁠highlight-yellow,不如考虑调用变量 -⁠-⁠accent-color,因为“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。尝试切换黑暗模式并重新加载页面:特定的当前不匹配的样式表仍然会加载,但优先级最低,因此它们永远不会与站点现在需要的资源竞争。

Network loading diagram showing how in light mode the dark mode CSS gets loaded with lowest priority
浅色模式下的站点以最低优先级加载黑暗模式 CSS。
Network loading diagram showing how in dark mode the light mode CSS gets loaded with lowest priority
黑暗模式下的站点以最低优先级加载浅色模式 CSS。
Network loading diagram showing how in default light mode the dark mode CSS gets loaded with lowest priority
在不支持 prefers-color-scheme 的浏览器上,默认浅色模式下的站点以最低优先级加载黑暗模式 CSS。

对黑暗模式更改做出反应

像任何其他媒体查询更改一样,可以通过 JavaScript 订阅黑暗模式更改。您可以使用它来动态更改页面的 网站图标 或更改 <meta name="theme-color">,它决定了 Chrome 中 URL 栏的颜色。上面的完整示例 展示了实际操作,为了查看主题颜色和网站图标更改,请在单独的选项卡中打开 演示

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 DevTools 现在允许您模拟用户的首选配色方案,这种方式只会影响当前可见的选项卡。打开 命令菜单,开始键入 Rendering,运行 Show Rendering 命令,然后更改 模拟 CSS 媒体功能 prefers-color-scheme 选项。

A screenshot of the 'Emulate CSS media feature prefers-color-scheme' option that is located in the Rendering tab of Chrome DevTools

使用 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) 这样的颜色效果很好。

重新着色并调暗摄影图像

如果您比较下面的两个屏幕截图,您会注意到不仅核心主题从 浅色背景上的深色文字 变为 深色背景上的浅色文字,而且英雄图像看起来也略有不同。我的 用户研究 表明,当黑暗模式处于活动状态时,大多数受访者更喜欢稍微不那么鲜艳和明亮的图像。我将其称为 重新着色

Hero image slightly darkened in dark mode.
黑暗模式下稍微调暗的英雄图像。
Regular hero image in light mode.
浅色模式下的常规英雄图像。

可以通过图像上的 CSS 滤镜来实现重新着色。我使用一个 CSS 选择器来匹配 URL 中不包含 .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 状态下的反转量。

Icons are inverted in dark mode.
图标在黑暗模式下被反转。
Regular icons in light mode.
浅色模式下的常规图标。

请注意,我再次仅在 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 图像,您可以利用 currentColor CSS 关键字,而不是 使用反转滤镜,它表示元素 color 属性的值。这使您可以在默认情况下不接收 color 值的属性上使用 color 值。方便的是,如果 currentColor 用作 SVG fillstroke 属性 的值,则它会从 color 属性的继承值中获取其值。更好的是:这也适用于 <svg><use href="…"></svg>,因此您可以拥有单独的资源,并且 currentColor 仍将在上下文中应用。请注意,这仅适用于 内联<use href="…"> SVG,但不适用于作为图像的 src 引用的 SVG 或以某种方式通过 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>

深色模式,但添加退出选项

正如在上面的 为什么选择深色模式 部分中提到的,对于大多数用户来说,深色模式是一种审美选择。因此,一些用户可能实际上喜欢将其操作系统 UI 设置为深色,但仍然希望以他们习惯的方式查看网页。一个很好的模式是最初遵循浏览器通过 prefers-color-scheme 发送的信号,然后可以选择允许用户覆盖其系统级设置。

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

您当然可以自己创建此代码,但您也可以直接使用我为此目的创建的现成的自定义元素(Web 组件)。它被称为 <dark-mode-toggle>,它为您的页面添加了一个切换开关(深色模式:开/关)或主题切换器(主题:浅色/深色),您可以完全自定义它。下面的演示展示了该元素的实际效果(哦,我还 🤫 静悄悄地将其偷偷添加到了所有 其他 示例 上方)。

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

尝试点击或轻触下面演示右上角的深色模式控件。如果您选中第三个和第四个控件中的复选框,请查看即使在您重新加载页面时,您的模式选择是如何被记住的。这允许您的访问者将其操作系统保持在深色模式,但可以在浅色模式下享受您的网站,反之亦然。

结论

使用和支持深色模式很有趣,并开辟了新的设计途径。对于您的一些访问者来说,这可能是他们能否顺利使用您的网站和成为快乐用户之间的区别。虽然存在一些陷阱,并且绝对需要仔细测试,但深色模式绝对是您展示您关心所有用户的一个绝佳机会。本文中提到的最佳实践和诸如 <dark-mode-toggle> 自定义元素之类的助手应该使您对创建出色的深色模式体验充满信心。在 Twitter 上告诉我您的创作,以及这篇文章是否有用,或者您是否有改进建议。感谢您的阅读!🌒

prefers-color-scheme 媒体查询的资源

color-scheme meta 标签和 CSS 属性的资源

通用深色模式链接

本文的背景研究文章

致谢

prefers-color-scheme 媒体功能、color-scheme CSS 属性和相关的 meta 标签是 👏 Rune Lillesveen 的实现工作。Rune 也是 CSS Color Adjustment Module Level 1 规范的共同编辑。我要 🙏 感谢 Lukasz ZbylutRowan MerewoodChirag DesaiRob Dodson 对本文的全面审查。加载策略Jake Archibald 的创意。Emilio Cobos Álvarez 向我指出了正确的 prefers-color-scheme 检测方法。关于引用的 SVG 和 currentColor 的技巧来自 Timothy Hatcher。最后,我感谢众多用户研究的匿名参与者,他们帮助塑造了本文中的建议。英雄图片由 Nathan Anderson 拍摄。