算法主题引擎
摘要
本文介绍了新的CSS `contrast-color()`函数,该函数允许开发人员自动选择黑色或白色文本,以实现与任何背景颜色的可访问对比度,解决网络上长期存在的低对比度问题,而无需依赖JavaScript。
暂无内容
查看缓存全文
缓存时间: 2026/06/04 03:44
# 算法化主题引擎:使用 contrast-color() 构建自校正色彩系统 — Smashing Magazine
来源:<https://www.smashingmagazine.com/2026/05/building-self-correcting-color-systems-contrast-color/> - 15 分钟阅读 - CSS (<https://www.smashingmagazine.com/category/css>), Coding (<https://www.smashingmagazine.com/category/coding>), Techniques (<https://www.smashingmagazine.com/category/techniques>)
2025 年,仍有 70% 的网站未能通过基本 WCAG 对比度检查。经过多年的设计系统工具、可访问性 lint 工具和 JavaScript 库的发展,情况并未改善。我们需要的不是更好的库,而是更好的 CSS。`contrast-color()` 就是那个更好的 CSS。
HTTP Archive Web Almanac (<https://almanac.httparchive.org/>) 多年来一直在跟踪色彩对比失败的情况。数字几乎没变。经过五年的设计系统工具、可访问性 lint 工具,以及整个专门用于计算可读文本颜色的 JavaScript 库,2025 年仍有 70% 的网站未能通过基本 WCAG 对比度检查 (<https://almanac.httparchive.org/en/2025/accessibility#color-contrast>)。WebAIM Million (<https://webaim.org/projects/million/>) 描绘了更严峻的画面——2026 年有 83.9% 的主页因低对比度文本被标记,高于 2025 年的 79.1%。这个比率在一个基准上每年仅改善几个百分点,而在另一个基准上实际上变得更糟。这不是进步——这证明依靠运行时 JavaScript 来处理如此基础的事情在开放网络上无法扩展。我们需要的不是更好的库,而是更好的 CSS。`contrast-color()` 函数就是那个更好的 CSS。一个声明。浏览器在样式计算期间运行对比数学,在页面绘制之前,然后给你正确的文本颜色。没有库,没有构建步骤,没有水合闪烁。
**注意**:如果你在旧文章和规范草案中看到它被称为 `color-contrast()`——那个名字被更改了 (<https://github.com/w3c/csswg-drafts/issues/7557>),而且旧语法在任何浏览器中都不再有效。
## 它做什么(以及不做什么)
Level 5 版本很简单。你给它一个颜色。它返回给你 `black` 或 `white`,哪个对你的输入有更高的对比度。
```css
.button {
background-color: var(--brand-color);
color: contrast-color(var(--brand-color));
}
```
将 `--brand-color` 改为霓虹绿,文本变黑色。改为深海军蓝,文本变白色。通过 JavaScript 在运行时切换主题,文本会立即适应——无需事件监听器,无需重新计算。
关于当前版本需要了解的一些事情:
- 它返回一个 `<color>`,而不是数字。你得到一个实际的颜色值(`black` 或 `white`),可以在 CSS 接受颜色的任何地方使用。
- **目前仅限黑色或白色**。候选颜色列表和目标对比度比率计划在 Level 6 中。
- **没有关键字。** 如果你在旧博客文章中看到过 `max`,它已从规范中移除。使用它会默默破坏你的声明。
- 如上所述,这个函数在早期草案中曾被称为 `color-contrast()`。那个名字已失效——CSSWG 已将其重命名 (<https://github.com/w3c/csswg-drafts/issues/7557>),以遵循 CSS 函数以其返回内容命名的惯例。`color-mix()` 返回一个颜色。`contrast-color()` 返回一个颜色。旧的 `color-contrast()` 名字听起来像是返回一个对比度比率(像 4.5 这样的数字),这具有误导性。任何显示 2021–2023 年 `color-contrast()` 语法的教程在当前浏览器中都不起作用。
## 规范拆分:Level 5 与 Level 6
这个函数存在于两个规范中。这不寻常,值得理解。
**CSS Color Level 5** (<https://www.w3.org/TR/css-color-5/#contrast-color>) 定义了浏览器今天提供的内容。一个颜色输入,黑色或白色输出。算法被特意标记为“UA 定义”,意味着浏览器决定内部使用什么数学。现在,每个引擎都使用 WCAG 2.x 相对亮度。但“UA 定义”标签并非偶然——它是一个计划好的逃生舱。
你会在这个上下文中看到很多关于 APCA(**Accessible Perceptual Contrast Algorithm**,可感知对比度算法)的讨论。APCA 模拟人类眼睛实际感知对比度的方式,考虑字体粗细、空间频率和环境光——这是对 WCAG 2.x 公式的真正改进。通过不将“使用 WCAG 2.x”锁定到 Level 5 规范中,浏览器供应商以后可以切换到 APCA,而不会破坏任何现有代码。如果规范在默认情况下带有 `wcag2()` 关键字发布,那么每个使用它的网站将永久停留在旧数学上。
但 APCA 的未来远不如炒作所暗示的那样确定。Adrian Roselli 的“WCAG3 Contrast as of April 2026” (<https://adrianroselli.com/2026/04/wcag3-contrast-as-of-april-2026.html>) 清楚地描述了当前情况:APCA 在 2023 年中期被从 WCAG 3 工作草案中移除 (<https://www.w3.org/TR/2023/WD-wcag-3.0-20230724/#color-and-contrast>),因为未能获得足够的工作组支持。WCAG 3 规范目前表示对比度算法“尚未确定”,并且该标准本身可能要到 2030 年或更晚才能最终确定 (<https://www.w3.org/WAI/standards-guidelines/wcag/wcag3-intro/#timeline>)。Roselli 还在 2024 年 5 月提交了一个 Chromium 问题 (<https://issues.chromium.org/issues/341439947>),要求从 DevTools 中完全移除“Advanced Perceptual Contrast Algorithm”实验标志,认为该实现已过时,并可能误导开发者认为 APCA 比实际情况更先进或更官方。该问题仍未解决。
所有这些并不意味着 APCA 已死。其背后的研究经过同行评审且内容充实的,它的创建者指出 (<https://adrianroselli.com/2026/04/wcag3-contrast-as-of-april-2026.html#comment-408186>),通过 APCA 指南的颜色在绝大多数情况下远超 WCAG 2 的最低要求。但目前,无法保证 APCA 会成为取代 WCAG 2.x 的算法——这种不确定性对 `contrast-color()` 至关重要。如果另一种算法胜出,或者 WCAG 3 采用全新的东西,“UA 定义”标签意味着浏览器可以适应而不会破坏你的代码。这也意味着 Level 6 的功能——候选颜色列表、目标比率、`tbd-fg`/`tbd-bg` 关键字——都是围绕一种可能不会以其当前形式实现的算法设计的。
**CSS Color Level 6** 添加了扩展语法——候选颜色列表和目标对比度比率:
```css
/* Level 6 未来语法 — 尚未发布 */
color: contrast-color(var(--bg) tbd-bg wcag2(aa), #1a1a2e, #e2e8f0, #fbbf24);
```
浏览器会从左到右评估每个候选颜色,并选择第一个满足 4.5:1 AA 阈值的颜色。`tbd-fg` 和 `tbd-bg` 关键字指示基础颜色是前景还是背景,这对于像 APCA 这样的方向性对比度模型很重要。这完全是工作草案领域——考虑到 APCA 的不确定状态,更是如此。目前请使用 Level 5 版本。
## 浏览器支持
这个比大多数新的 CSS 特性更好。所有三大引擎都已在稳定版本中发布:Chrome 147 (<https://chromestatus.com/feature/40142548>)(2026 年 4 月)、Firefox 146 (<https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/contrast-color>) 和 Safari 26.0 (<https://developer.apple.com/documentation/safari-release-notes/safari-26-release-notes>)。它于 2026 年 4 月达到 Baseline Newly Available 状态 (<https://web.dev/baseline/>)。查看 caniuse (<https://caniuse.com/mdn-css_types_color_contrast-color>) 获取完整版本矩阵。
所有三个引擎都通过了 `contrast-color()` 的 Web Platform Tests,这意味着边缘情况(例如,打破平局的逻辑、色彩空间转换、语法解析)在不同浏览器中表现一致。caniuse 上的原始全球支持百分比看起来较低,但这主要反映了企业浏览器和从不更新的人。如果你正在阅读本文,你的浏览器几乎肯定已经支持它。使用 `@supports` 进行渐进增强很简单:
```css
.card {
background: var(--bg);
color: #fff;
text-shadow: 0 0 4px rgb(0 0 0 / 0.8);
}
@supports (color: contrast-color(red)) {
.card {
color: contrast-color(var(--bg));
text-shadow: none;
}
}
```
旧浏览器获得带有深色阴影的白色文本以确保可读性。支持浏览器获得原生计算。没有人看到损坏的文本。
需要注意的一件事:自动化可访问性扫描器(Lighthouse、Axe 等)无法评估 `text-shadow`。它们只查看计算的 `color` 与 `background-color`。因此,即使在人类眼中阴影使文本完全可读,后备方案在 CI/CD 管道中仍会被标记为对比度失败。如果你的团队运行自动化 a11y 检查,你可能需要将该特定规则加入允许列表,或添加注释解释为什么该标志是误报。
> **关于 PostCSS 的说明**:有一个插件 (`@csstools/postcss-contrast-color-function` (<https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-contrast-color-function>)) 可以在构建时评估 `contrast-color()`。它适用于静态颜色,如 `contrast-color(#ff0000)`。但一旦你使用自定义属性——`contrast-color(var(--bg))`——插件就无法帮助,因为它无法访问运行时值。如果你的主题是动态的(这正是这样做的意义),请跳过 polyfill 并依赖 `@supports`。
## 陷阱
### 它不保证感知或 AAA 合规
这可能会让人绊倒:“我用了对比度函数,所以我的网站现在通过了可访问性检查,对吗?” 数学上?通常是的。有一个持续存在的误解,认为对于某些“中间色调”背景,黑色和白色都未能通过标准的 WCAG 4.5:1 AA 比率。这在数学上是错误的。在 WCAG 2.x 相对亮度公式下,绝对没有任何背景颜色会让纯黑色和纯白色都未能通过 AA。其中一个(或两个)总会通过。
以 `#2277d3`(一种中蓝色)为例。它正好位于数学边缘,实际上黑色和白色都通过了 AA(两者都达到大约 4.58:1)。`contrast-color()` 会给你稍有数学优势的那个。但实际的陷阱是:WCAG 2.x 数学已知存在感知盲点。同样的 `#2277d3` 配黑色文字在数学上通过了 AA,但对人眼来说,可能非常难以阅读。`contrast-color()` 给你的是*数学*合规,这对自动化审计很好,但并不总是等于*感知*可访问性。(这正是 APCA 存在的原因,以及规范设计为允许浏览器以后切换算法的原因。)
此外,如果你的目标是更严格的 WCAG AAA 标准(7.0:1),确实存在真正的死区。对于亮度大致在 10% 到 30% 之间的背景,黑色和白色都不能达到 7:1。在这些情况下,`contrast-color()` 无法拯救你——它只是给你“最不坏”的失败选项。
### 过渡会突变而非渐变
如果你在悬停时将背景从 `white` 动画到 `black`:
```css
.btn {
background-color: #fff;
color: contrast-color(#fff); /* black */
transition: background-color 1s, color 1s;
}
.btn:hover {
background-color: #000;
color: contrast-color(#000); /* white */
}
```
背景在 1 秒内平滑渐变。但由于 Level 5 的输出是一个离散值 (<https://www.w3.org/TR/css-transitions-1/#discrete>)(黑色或白色),文本颜色无法插值。它会突变。这里是视觉陷阱:突变不会发生在中途。如果你已经制作主题一段时间,你可能对旧 Sass 时代有肌肉记忆,那时我们检查 `lightness($bg) > 50%`。那依赖于 HSL 亮度,其中 50% 是几何中点。但 WCAG 2.x 相对亮度是一个非线性尺度。在 WCAG 公式下,数学转折点——黑色和白色对背景具有相同对比度的点——实际发生在大约 18% 的相对亮度(具体是 ~17.9%)。因此,在白色到黑色的渐变过程中,视觉行为严重偏斜。文本在动画的绝大部分时间保持黑色,只在背景变得极暗时的过渡末尾才突变到白色。这是一个刺耳的、迟来的硬切换。
你可能认为 `transition-behavior: allow-discrete` (<https://css-tricks.com/almanac/properties/t/transition/transition-behavior/>) 可以解决这个问题。但它不能。`allow-discrete` 并没有修复刺耳的视觉体验,因为它无法插值二进制输出;它只是将硬切换的时间点移到动画持续时间的 50% 标记处。如果你需要平滑的文本颜色过渡,你必须分层使用 `color-mix()` 或自己管理交叉淡入淡出。
### 平局时白色胜出
如果背景是一个完美的中间灰色,黑色和白色产生相同的对比度比率,规范有一个硬编码的平局判定 (<https://www.w3.org/TR/css-color-5/#contrast-color>):白色胜出。实践中不是什么大问题,但如果你调试灰色调色板而文本没有按预期表现,则值得了解。
### 渐变和图像不支持
该函数接受一个平坦的 `<color>` 值。你不能传递渐变或 `url()`。`contrast-color(linear-gradient(...))` 是一个解析错误。如果你的背景是照片或复杂渐变,你仍然需要 JavaScript 或手动选取颜色用于覆盖文本。
### 透明颜色会先合成
传递半透明颜色时,浏览器会在运行对比度数学之前将其与假设的不透明画布(通常是白色)混合。它并没有忽略你的 alpha 通道——它正在合成它。但如果你期望该函数“看穿”到元素后面的实际内容,结果可能会让你惊讶。
### Windows 高对比度模式
如果用户启用 Windows 高对比度,`forced-colors: active` (<https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors>) 媒体查询生效,浏览器会激进地覆盖作者定义的颜色。`contrast-color()` 退出——像 `CanvasText` 这样的强制系统颜色 (<https://www.w3.org/TR/css-color-adjust-1/#forced-colors-mode>) 完全接管。你无需编写手动媒体查询来撤消你的对比度逻辑;浏览器处理层次结构。
## 与其他颜色函数结合使用
黑色或白色听起来很有限,但一旦你将那个输出输入其他 CSS 颜色函数,你就可以从单个自定义属性构建整个组件调色板。
### 使用相对颜色语法实现品牌色调对比度
纯黑色文本在鲜艳卡片上看起来不错。纯白色在珊瑚色卡片上可能显得平淡。如果对比文本是背景颜色的非常深或非常浅的色调呢?Kevin Hamer 在他 CSS-Tricks 的文章“使用其他 CSS 特性近似 contrast-color()” (<https://css-tricks.com/approximating-contrast-color-with-other-css-features/>) 中探索了相关领域,他使用 OKLCH 亮度和 `round()` 来*没有* `contrast-color()` 的情况下近似黑/白切换——基本上是 `oklch(from round(1.21 - l) 0 0)`。那是一种 polyfill 策略:在不支持原生函数的浏览器中实现二进制亮/暗判定。
我们在这里做的是不同的——我们*从* `contrast-color()` 的原生输出开始,然后通过注入背景本身的色调来丰富它:
```css
.card {
--bg-hue: 260; /* 靛蓝 */
--bg: oklch(0.6 0.1 var(--bg-hue));
background: var(--bg);
/* 从黑/白对比度颜色中提取 L,但注入微妙的色度和背景的色调 */
color: oklch(from contrast-color(var(--bg)) l 0.05 var(--bg-hue));
}
```
当 `contrast-color()` 返回白色时,`l` 为 1(全亮度)。当它返回黑色时,`l` 为 0。通过将背景的色调重新拉入并添加一点色度,你得到的文本读起来像深暗的靛蓝或浅冷的靛蓝,而不是
相似文章
仅用CSS指定每主题颜色的几种方法
本文介绍了五种仅用CSS实现每主题颜色(浅色/深色/自动)的技术,无需JavaScript,利用prefers-color-scheme、:has()、CSS变量和color-mix()等特性。
深色模式的六个层次(2024)
一篇探讨网页设计中深色模式实现的六个层次的文章,范围从基础的meta标签方法到使用CSS的高级配色方案切换技术。
readable.css
readable.css 是一个CSS框架,为网站提供了合理且美观的基础默认样式,支持亮/暗模式、响应式设计和垂直节奏,强调一致性和语义化HTML。
Area Contrast Checker
Area Contrast Checker 是一款通过拖拽、选择并了解对比度比值来检查无障碍对比度的工具。
使用 CSS 为文本、图像和表格实现垂直节奏
本文探讨了在网页设计中使用 CSS 实现垂直节奏,特别是利用 `rlh` 单位进行文本对齐,并提供了一种 JavaScript 变通方案,以便响应式图像保持一致的间距。