高对比度模式 (HCM) 媒体查询

Firefox 支持 prefers-contrastforced-colors 媒体查询。这些查询检测用户的显示设置(在浏览器中和/或操作系统中),并允许作者相应地调整内容样式。这些查询不能互换,在设计辅助功能时,我们应该有意使用其中一个或两个(或两者)!

是什么激活了这些查询?

强制颜色

forced-colors 媒体查询有两个值:activenone。此媒体查询的解析值由浏览器根据用户设置(操作系统和 Firefox)确定。

  • none 状态表示文档可以使用任何颜色来渲染内容。

  • active 状态表示用户启用了操作系统或浏览器设置,这些设置限制了用户代理可用的调色板。

在 Firefox 的默认配置中,当用户启用了 Windows 高对比度模式或Firefox 高对比度模式 (FF HCM) 时,会触发 active 状态。这两种强制颜色模式都为用户提供了有限的颜色自定义选项。在 Windows 中,用户可以覆盖七种颜色,而在 Firefox 高对比度模式下,用户可以覆盖四种颜色。MacOS 的“提高对比度”设置**不会**触发 forced-colors: active,因为它不会限制用户代理可用的调色板。

注意:如果在 macOS 上启用“提高对比度”设置并同时启用 FF HCM 的“仅在高对比度主题下”选项,则**会**触发 forced-colors: active(在非 Chrome 网页内容中)。在 Linux 上也是如此。因为FF HCM 在非 Windows 平台上默认为“从不”,所以单独启用“提高对比度”或 Linux 高对比度主题不会触发 forced-colors: active。同样,如果用户在启用 Win HCM 的情况下禁用 FF HCM,则 forced-colors(在内容中)将评估为 none

CSS 系统颜色

上面列出的强制颜色模式覆盖的颜色会被继承到浏览器的CSS 系统颜色中。在为强制颜色模式设计时,设计师必须专门使用这些颜色。CSS 系统颜色旨在成对使用:例如,ButtonFaceButtonText 颜色应一起使用来创建交互式控件的样式。完整的配对列在 CSS 规范的系统颜色配对部分中。Mozilla 在我们的HCM 设计指南中提供了使用 CSS 系统颜色的其他设计指南。您还可以查看我们的其他文档,以了解有关颜色覆盖的更多信息

首选对比度

prefers-contrast 媒体查询有四个值:morelesscustomno-preference

  • 当用户启用了 macOS 的“提高对比度”设置,或者强制颜色模式处于活动状态**并且**用户选择的前景和背景之间的比率高于 7:1 时,会触发 more 状态。此检查在我们的样式系统中完成。

  • 当强制颜色模式处于活动状态且前景/背景比率低于 4.5 时,会触发 less 状态。

  • 当启用强制颜色模式且比率在这两个值之间时,我们会返回 custom

  • 否则,如果未启用强制颜色模式,并且 macOS 的“提高对比度”已关闭,则媒体查询将解析为 no-preference

那么我应该使用哪个?

通常,这取决于您希望样式应用于哪些操作系统。过去,我们经常编写代码来定位 prefers-contrast 块中的所有平台,因为我们为定位强制颜色模式所做的更改也使 macOS 上的“提高对比度”用户受益;它们允许系统颜色传播到用户代理。macOS 在启用“提高对比度”时提供的调色板也会被继承到 CSS 系统颜色中,并且可能比标准调色板为设计提供改进。强制颜色模式的更改仍然可能使“提高对比度”用户受益,但是我们强烈建议开发人员考虑我们的设计是否针对这些模式进行多样化,从而提供更好的用户体验。

在 macOS 上,我们不限于操作系统调色板。我们可以简单地将现有颜色的对比度比率提高到 7:1,以满足网页内容无障碍指南 (WCAG) 的增强对比度标准,以获得更好的辅助功能,而不是修改设计以使用更少的颜色。请考虑以下设计。

macOS 上的标准用户体验

Firefox view page. The navigation tabs are rendered to match the custom browser theme -- yellow on dark grey, with the active tab rendered light Firefox blue on blue-grey. Page content is rendered yellow on dark grey. Interactive components are a slightly lighter grey, difficult to distinguish against the page background.

macOS 上使用增强对比度的现有颜色的用户体验

Firefox view page. The navigation tabs are rendered yellow on darker grey, with the active tab rendered white on darker blue. Page content is rendered yellow on dark grey with white borders around interactive components.

此示例中存在的更改应位于 prefers-contrast: more 块中。这是**假设这些更改在级联中被适当覆盖以供 forced-colors: active 用户使用**。如果它们未被覆盖,则应将 @media (-moz-platform: macos).. and (not (forced-colors)) 之类的其他查询与 prefers-contrast: more 结合使用,以确保这些更改仅适用于 macOS。对于偏好较低对比度的用户,我们可以在现有设计中提供降低对比度比率的颜色,并将这些更改包含在 prefers-contrast: less 块中。

macOS 上 FF HCM 中有限调色板的用户体验

Firefox view page. The navigation tabs are rendered white on dark grey, with the active tab rendered black on white. Page content is rendered white on black with borders around interactive components.

在强制颜色模式下,我们需要设计减少其调色板以实现兼容性。为此,我们使用 CSS 系统颜色而不是原始设计颜色。这允许覆盖的调色板“显示出来”,因为来自各种强制颜色模式的颜色会被继承到 CSS 系统颜色中。使用 CSS 系统颜色也可能会提高 macOS 用户的对比度,但这会更大幅度地改变手头的设计,并且可能会降低那些只是希望获得更高对比度的用户的整体用户体验。设计师应考虑我们HCM 设计指南中有关边框使用、移除渐变等的额外指南。

注意:Firefox 的 Chrome 样式表受与 Web 作者样式表不同的规则约束。

Firefox 的 Chrome 样式表(包括我们about: 页面中的样式表)不会阻止使用非系统颜色,即使启用了强制颜色模式也是如此。但是,在网页中,无论作者的原始规范如何,Firefox 都会强制使用 CSS 系统颜色。这是一种安全机制,可确保即使未指定 @media (prefers-contrast)@media (forced-colors) 样式,页面也能正确呈现。为了避免这种覆盖,Web 作者可以使用forced-color-adjust: none; CSS 属性。

编写可维护的前端代码

在可能的情况下,我们更喜欢在 root 级别的一个块中进行 prefers-contrastforced-colors 的颜色覆盖,而不是在逐个元素的基础上进行多个块的覆盖。我们鼓励开发人员在覆盖时使用设计系统令牌并使用语义命名。请考虑以下内容。

:root {
  --page-background: #cccccc;
  --page-text-color: #000000;

  @media (forced-colors) {
    --page-background: Canvas;
    --page-text-color: CanvasText;
  }
}

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

在这里,我们在 :root 上定义了一组颜色变量,并在 forced-colors 块中随后覆盖了它们。我们不需要在媒体查询中重新编写 body 的样式,因为当 forced-colors 处于活动状态时,它引用的变量会正确地进行调整。

虽然 Firefox Chrome 样式表中不会发生 forced-colors 覆盖,但我们的样式系统确实会强制将所有 transparent 实例强制为颜色属性的默认颜色(通常为 CanvasText)。这使我们能够指定仅在强制颜色模式下显示的样式,而无需额外的 CSS 关键字。有时在 :root 级别定义具有初始值(例如 transparent0pxnone)的变量也很有益,以便稍后存在 HCM 覆盖的变量(参见:--dialog-border-width)。

:root {
  /* ... */
  --dialog-background: #ffffff;
  --dialog-color: #bbbbbb;
  --dialog-border-color: transparent;
  --dialog-border-width: 0px;

  @media (forced-colors) {
    /* ... */
    --dialog-background: Canvas;
    --dialog-color: CanvasText;
    /* No override for --dialog-borer-color, since it'll become
    * CanvasText when HCM is enabled, which matches the HCM spec. */
    --dialog-border-width: 1px;
  }
}

.dialog {
  color: var(--dialog-color);
  background-color: var(--dialog-background);
  border: var(--dialog-borer-width) solid var(--dialog-border-color);
}

/* ... */

通常,最好在 :root 级别进行覆盖,即使需要其他变量也是如此。**不建议**在逐类或逐元素的基础上进行覆盖,如下所示

:root {
  /* ... */
  --button-background: #ffffff;
  --button-text-color: #bbbbbb;

  @media (forced-colors) {
    /* ... */
    --button-background: ButtonFace;
    --button-text-color: ButtonText;
    --button-border-color: var(--button-text-color);
  }
}

@media (forced-colors) {
  /* BAD: These rules are generic and should be outside of a @media block */
  .destroyButton {
    color: var(--button-text-color);
    background-color: var(--button-background);
    border: 1px solid var(--button-border-color);
  }
}

@media (not (forced-colors)) {
  /* BAD: These rules are generic and should be outside of a @media block */
  .destroyButton {
    color: var(--light-grey-20);
    background-color: var(--red-60);
  }
}
/*...*/

综合起来!

让我们来看一下这个示例网站上的一个例子。

我们网站的大部分样式都是通过覆盖根块的颜色来实现的。

:root {
  /* General */
  --background: #202124;
  --color: #cccccc;
  --border-color: transparent;
  --border-size: 0;
  --table-background: #303134;
  /* Note */
  --note-background: gold;
  --note-color: var(--table-background);
  --note-opacity: .7;
  /* General Controls */
  --control-background: gray;
  --control-color: var(--color);
  --control-border-size: var(--border-size);
  --control-border-color: var(--border-color);
  /* Destructive Button */
  --destructive-button-background: tomato;
  --destructive-button-color: var(--table-background);
}

我们如何才能适应那些更喜欢高对比度用户的网站呢?对于强制颜色模式的用户来说又该如何呢?

让我们从更喜欢高对比度的用户开始。我们可以通过使前景色文本更亮,增加对比度比率,来决定使背景和前景色更容易阅读。我们还可以移除注释的不透明度并使其文本颜色变暗。我们不需要移除注释的金色背景,因为 prefers-contrast 不需要减少颜色调色板。

:root {
  /* ... */
  @media (prefers-contrast) and (not (forced-colors)) {
    /* General */
    --color: white;
    /* Note */
    --note-color: black;
    --note-opacity: 1;
    /* ... */
  }
}

为了在强制颜色模式下解决这些相同的问题,我们应该用语义上合适的系统颜色替换颜色。与我们在上面 prefers-contrast 块中所做的工作不同,我们需要修改注释的背景,因为 gold 不是系统颜色。我们最终可能会得到类似这样的东西

:root {
  /* ... */
  @media (forced-colors) {
    /* General */
    --background: Canvas;
    --color: CanvasText;
    --table-background: var(--background);
    /* Note */
    --note-background: var(--background);
    --note-color: var(--color);
    --note-opacity: 1;
    /* ... */
  }
}

更改之后,您会注意到我们的页面背景、表格背景和注释背景都共享相同的颜色。这使得它们难以区分。为了解决这个问题,我们可以添加一个对比边框并覆盖之前透明的 --border-color 变量及其对应的 --border-size。此变量适用于内容区域,但不适用于控件。

:root {
  /* ... */
  @media (forced-colors) {
    /* General */
    --background: Canvas;
    --color: CanvasText;
    --border-color: var(--color);
    --border-size: 1px;
    --table-background: var(--background);
    /* Note */
    --note-background: var(--background);
    --note-color: var(--color);
    --note-opacity: 1;
    /* ... */
  }
}

接下来,让我们看看此页面用于 Web 表单的控件。我们有一个输入框、一个复选框、一个提交按钮和一个清除按钮。在 prefers-contrast 的情况下,我们应该确保控件与背景尽可能地形成对比。可以通过颜色来实现这一点,但添加边框可以帮助增强对比度。在这里,我们也不需要去除清除按钮的 tomato 背景,但可以将其更新为更亮的色彩。

:root {
  /* ... */
  @media (prefers-contrast) and (not (forced-colors)) {
    /* ... */
    /* General Controls */
    --control-background: color-mix(in srgb, gray, lightgray);
    --control-color: black;
    --control-border-size: 1px;
    --control-border-color: gray;
    /* Destructive Button */
    --destructive-button-background: red;
    --destructive-button-color: white;
    /* ... */
  }
}

最后,让我们为 forced-colors 样式化页面的控件。与 prefers-contrast 不同,我们不能使用颜色来区分提交按钮和清除表单按钮 - 两者都应从我们的按钮 CSS 系统颜色继承。

:root {
  /* ... */
  @media (forced-colors) {
    /* General */
    --background: Canvas;
    --color: CanvasText;
    --border-color: var(--color);
    --border-size: 1px;
    --table-background: var(--background);
    /* Note */
    --note-background: var(--background);
    --note-color: var(--color);
    --note-opacity: 1;
    /* General Controls */
    --control-background: ButtonFace;
    --control-color: ButtonText;
    --control-border-color: ButtonText;
    /* Destructive Button */
    --destructive-button-background: var(--control-background);
    --destructive-button-color: var(--control-color);
  }
}

如果我们想使提交按钮作为主要按钮脱颖而出,我们可以反转该按钮的样式。例如:

:root {
  /* ... */
  @media (forced-colors) {
    /* General */
    --background: Canvas;
    --color: CanvasText;
    --border-color: var(--color);
    --border-size: 1px;
    --table-background: var(--background);
    /* Note */
    --note-background: var(--background);
    --note-color: var(--color);
    --note-opacity: 1;
    /* General Controls */
    --control-background: ButtonFace;
    --control-color: ButtonText;
    --control-border-color: ButtonText;
    /* Destructive Button */
    --destructive-button-background: var(--control-background);
    --destructive-button-color: var(--control-color);
  }

  #submit {
    background: var(--control-color);
    color: var(--control-background);
    border: var(--control-border-size) solid currentColor;
  }
}

现在我们有一个可以在多种 HCM 场景下运行的网站 :) 访问启用了 HCM 或“增加对比度”的 现场网站 以亲自测试它。一些收获

  • prefers-contrastforced-colors 不是互斥的。如果您编写两个独立的 forced-colorsprefers-contrast 媒体查询块,当启用 FF HCM 时它们都会应用(假设您的前景/背景对比度比率很高,默认情况下它就是)。在您的 @media (prefers-contrast) 声明中添加一个 and (not (forced-colors)) 子句可以帮助使这两个块区分开来,如果您希望在一个块中使用 Mac 相关的样式,而在另一个块中使用 forced-colors 样式。

  • prefers-contrast 需要特定、逐案的覆盖,而 forced-colors 主要与继承有关。在前者中,用颜色区分的页面区域应保持原样(尽管颜色对比度更高)。在 forced-colors 中,所有非交互式页面区域都应使用 Canvas/CanvasText 并依靠边框来区分。例如,一旦您在根级别设置了 --background: Canvas;,后续的背景变量应该从它继承。

  • 在可能的情况下,尝试使用 CSS 变量在 :root 级别进行覆盖,这使得将来更容易更新代码。