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

过度宣传或必要性?详细了解深色模式以及如何支持深色模式,让您的用户受益!

简介

先于深色模式,再采用深色模式

绿幕计算机显示器
绿幕(来源

我们使用深色模式完成了整个流程。 在个人计算时代来临,深色模式不是应选择的问题,但事实上是一个重要的问题:单色 CRT 计算机显示器通过在荧光屏幕上发射电子束来工作,早期 CRT 中使用的荧光体是绿色的。由于文本以绿色显示,而屏幕的其余部分为黑色,这些模型通常称为“绿屏”。

深白色文字处理
白底深色(来源

随后引入的彩色 CRT 通过使用红色、绿色和蓝色荧光体来显示多种颜色。他们通过同时激活所有三种荧光粉生成白色。 随着更复杂的所见即所得 桌面发布技术的出现,使虚拟文档模仿纸质纸质的想法开始流行。

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

从这里开始,“白底黑白”开始成为一种设计趋势,这种趋势在早期基于文档的 Web 环境中得以体现。 史上第一款浏览器 WorldWideWeb(请注意,CSS 还没发明)就是通过这种方式来显示网页。趣味小知识:有史以来第二款基于终端的浏览器行模式浏览器(一种基于终端的浏览器)在深色下显示为绿色。如今,网页和 Web 应用通常在浅色背景下使用深色文本进行设计,深色文本是一种基本假设,也已硬编码到用户代理样式表(包括 Chrome 的样式表中)。

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

CRT 的时代已经过去了。内容消费和创作已转向采用背光 LCD 或节能 AMOLED 屏幕的移动设备。更小、便于携带的计算机、平板电脑和智能手机催生了新的使用模式。 网络浏览、休闲编程和高端游戏等休闲任务常常发生在昏暗的下班后。人们甚至喜欢在晚上睡觉时在床上摆放设备。 人们在黑暗环境中使用设备的次数越多,“追求黑暗之道”的想法就越流行。

为什么选择深色模式

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

当人们被问及为什么喜欢或想要深色模式时,最热门的回答是“这样更舒服”,然后是“它既优雅又美观”。 Apple 在深色模式开发者文档中明确写道:“对于大多数用户而言,选择是启用浅色还是深色外观是很美观的事情,可能与环境光照条件无关。”

Mac OS 系统 7 中的 CloseView 包含
系统 7 CloseView(来源

作为无障碍工具使用深色模式

还有一些人实际上需要深色模式,并将其用作另一种无障碍工具,例如弱视用户。我能找到的此类无障碍工具最早出现的是 System 7CloseView 功能,具有白底黑底黑底白字切换开关。 虽然系统 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(如果可用)在桌面设备和移动设备(如果可用)上均支持 prefers-color-scheme(自版本 76)、Firefox(自 67 版起)、Safari(自 macOS 12.1 版起以及 iOS 13 版起)。对于其他所有浏览器,您可以参阅“Can I use support table”(我可以使用支持表格)。

在请求时了解用户的偏好

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

深色模式实际使用

最后,我们来了解一下支持深色模式的实际效果。与高地人一样,深色模式只能有一个:深色或浅色,但绝不能两者并用! 我为什么提到这一点?因为这种情况应该对加载策略产生影响。请勿强制用户在关键渲染路径中下载针对他们当前不使用的模式的 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,这两个变量实质上会创建一个“dark-on-light”和“light-on-dark”基准主题。

/* 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 变量即可。 使用深色模式时,语义整理样式会有很大帮助。例如,不妨考虑调用变量 -⁠-⁠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 订阅深色模式更改。例如,您可以使用此参数动态更改网页的网站图标,或更改决定 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" />

调试和测试深色模式

在开发者工具中模拟 prefers-color-scheme

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

Chrome DevTools 的“Rendering”(渲染)标签页中“Emulate CSS media feature preferreds-color-scheme”选项的屏幕截图

使用 Puppeteer 为 prefers-color-scheme 截屏

Puppeteer 是一个 Node.js 库,它提供了一个通过 DevTools 协议控制 Chrome 或 Chromium 的高阶 API。借助 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) 之类的代码运行良好。

重新着色和调暗照片图片

如果您比较以下两个屏幕截图,会发现不仅核心主题从 dark-on-light 更改为 light-on-dark,而且主打图片看起来也略有不同。我的用户研究表明,在深色模式处于启用状态时,大多数受访用户更喜欢色彩不那么鲜艳、画面鲜明的图片。我将这种情况称为重新着色

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

重新着色可以通过对图片使用 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 值的属性上使用该值。一般来说,如果将 currentColor 用作 SVG fillstroke 属性的值,则它会从颜色属性的继承值中获取其值。更好的是:这也适用于 <svg><use href="…"></svg>,因此您可以拥有单独的资源,并且仍会在上下文中应用 currentColor。请注意,这仅适用于内嵌<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 组件)。它称为 <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> 自定义元素等帮助程序应该可以让您充满信心地打造令人惊叹的深色模式体验。请在 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