为什么前端必须关心颜色对比度
假设你做了一个登录页:品牌蓝按钮 + 白色文字,设计师觉得「很漂亮」,PM 觉得「很品牌」,但上线后收到反馈:「我在户外看不清按钮上的字」「我是色弱,文字和背景糊在一起」。
这就是颜色对比度问题。WCAG(Web Content Accessibility Guidelines,Web 内容无障碍指南)是 W3C 制定的国际标准,美国 ADA、欧盟 EN 301 549、中国信息无障碍国家标准都直接引用或参考 WCAG。许多国家的法律要求政府网站、商业网站达到 WCAG AA 级。
对比度过低会影响:
- 色弱用户(约人口 8%,男性高达 12%):难以区分低对比色
- 老年用户:晶状体老化导致对低对比度敏感度下降
- 强光环境:户外、车内、办公室过亮时屏幕反光降低有效对比度
- 低分辨率设备:小字号 + 低对比 = 双重难读
配套工具:颜色对比度检查工具
WCAG 2.1 对比度的数学定义
相对亮度(Relative Luminance)
WCAG 不直接用 RGB 值算对比度,而是先转换为相对亮度 L,模拟人眼对不同颜色的敏感度(绿色最敏感,蓝色最不敏感)。
每个 RGB 通道(0-255)先做 gamma 解码:
function channel(n) {
const c = n / 255;
// 0.03928 是 WCAG 2.1 修正后的阈值(原 0.04045)
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
}
然后按人眼敏感度加权求和:
function relativeLuminance({ r, g, b }) {
return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
}
- 纯白(255, 255, 255)L = 1.0
- 纯黑(0, 0, 0)L = 0.0
- 纯绿(0, 255, 0)L ≈ 0.7152(最亮的原色)
- 纯蓝(0, 0, 255)L ≈ 0.0722(最暗的原色)
对比度比值(Contrast Ratio)
两色的对比度公式:
对比度 = (L1 + 0.05) / (L2 + 0.05)
其中 L1 为较亮色的相对亮度,L2 为较暗色的相对亮度。加 0.05 是为了补偿显示器的最低亮度(不是纯黑)和环境光反射,避免在低亮度区间得到过高的比值。
取值范围 1.0(同色)到 21.0(纯黑 vs 纯白)。
function contrastRatio(rgb1, rgb2) {
const L1 = relativeLuminance(rgb1);
const L2 = relativeLuminance(rgb2);
const lighter = Math.max(L1, L2);
const darker = Math.min(L1, L2);
return (lighter + 0.05) / (darker + 0.05);
}
WCAG 2.1 评级标准
WCAG 分为三个合规级别:A(最低)、AA(行业基准)、AAA(最高,特殊场景才要求)。
| 评级 | 应用场景 | 对比度阈值 | 说明 |
|---|---|---|---|
| AA | 普通文字(< 18px 或 < 14px 加粗) | ≥ 4.5:1 | 正文、链接、按钮文字 |
| AA | 大文字(≥ 18px 或 ≥ 14px 加粗) | ≥ 3.0:1 | 标题、大号强调文字 |
| AAA | 普通文字 | ≥ 7.0:1 | 增强对比度,医疗 / 政府关键信息 |
| AAA | 大文字 | ≥ 4.5:1 | 增强对比度 |
| AA | UI 组件(按钮边框、图标、表单控件) | ≥ 3.0:1 | 非文字元素边界 |
为什么大文字阈值低?因为大字号本身就更易读,「易读性 = 字号 × 对比度」,大字号可以抵消部分对比度不足。
为什么 AA 普通文字是 4.5:1 而不是 5:1?这是 W3C 基于色弱用户实测得出的阈值,4.5:1 时约 95% 的色弱用户能正常阅读。
常见配色场景对比度速查
以下是常见配色的对比度(用 对比度检查工具 验证):
| 前景色 | 背景色 | 对比度 | AA 普通文字 | AAA 普通文字 |
|---|---|---|---|---|
#000000 黑 | #ffffff 白 | 21.0:1 | ✓ | ✓ |
#1f2937 深灰 | #ffffff 白 | 14.7:1 | ✓ | ✓ |
#374151 中灰 | #ffffff 白 | 10.3:1 | ✓ | ✓ |
#6b7280 浅灰 | #ffffff 白 | 4.8:1 | ✓ | ✗ |
#9ca3af 更浅灰 | #ffffff 白 | 2.9:1 | ✗ | ✗ |
#2b6cff 品牌蓝 | #ffffff 白 | 4.0:1 | ✗ | ✗ |
#1e3a8a 深蓝 | #ffffff 白 | 8.6:1 | ✓ | ✓ |
#dc2626 红 | #ffffff 白 | 4.3:1 | ✗ | ✗ |
#16a34a 绿 | #ffffff 白 | 3.4:1 | ✗ | ✗ |
#ffffff 白 | #2b6cff 品牌蓝 | 4.0:1 | ✗ | ✗ |
注意几个反直觉点:
- 品牌蓝
#2b6cff文字在白底上不达 AA(4.0:1 < 4.5:1),但作为按钮背景 + 白色文字同样不达 AA。 - 浅灰
#9ca3af作为正文色不达 AA,常见于「次要信息」灰色文字,但若作为正文则违规。 - 绿色
#16a34a在白底上不达 AA,常见于「成功提示」,但作为正文文字不合规。
品牌色不达标的 4 种解决方案
品牌色(如公司 Logo 色)通常饱和度高、明度中等,与白色背景对比度往往不足 4.5:1。4 种解决思路:
方案 1:加深品牌色作为文字色
保留原品牌色用于按钮背景、图标等大面积色块,文字色使用更深的同色相变体。
:root {
--brand-primary: #2b6cff; /* 原品牌色,按钮背景 */
--brand-text: #1e3a8a; /* 加深变体,文字色,对比度 8.6:1 */
}
.text-brand { color: var(--brand-text); }
.btn-primary { background: var(--brand-primary); color: #fff; }
用 颜色格式转换工具 把品牌色转 HSL,把 L(亮度)降低 20% 左右通常即可达标。
方案 2:加大字号使其符合「大文字」标准
大文字(≥ 18px 或 ≥ 14px 加粗)只需 ≥ 3.0:1。如果品牌色对比度 3.5:1,把字号从 14px 提到 18px即可合规。
.brand-heading {
color: #2b6cff; /* 4.0:1 不达普通 AA */
font-size: 18px; /* 但符合「大文字」标准,3.0:1 即达标 */
font-weight: 400;
}
方案 3:更换背景色
把白底改为浅灰底,差异虽小但有时足够。#2b6cff 在 #ffffff 上是 4.0:1,在 #f9fafb 上是 4.05:1(仍不达标),但在 #f3f4f6 上是 4.13:1(更接近但仍不达标)。这个方案效果有限,通常需要与方案 1 结合。
方案 4:使用描边或下划线辅助
对于链接、按钮等可交互元素,如果对比度仅勉强达标,额外加下划线或描边提供视觉冗余。WCAG 1.4.1(颜色用途)要求「不能仅靠颜色传达信息」,下划线既符合这条又提升可读性。
a.brand-link {
color: #2b6cff;
text-decoration: underline; /* 不依赖颜色识别 */
text-underline-offset: 2px;
}
暗色模式下的对比度陷阱
许多人以为「暗色背景天然高对比」,这是错的。暗色模式同样需要满足 WCAG 对比度要求,常见陷阱:
| 配色 | 对比度 | 问题 |
|---|---|---|
#9ca3af 文字 + #1f2937 背景 | 4.5:1 | 勉强达 AA,但小字号下仍难读 |
#6b7280 文字 + #1f2937 背景 | 2.7:1 | 不达 AA,常见于「次要文字」 |
#374151 文字 + #1f2937 背景 | 1.4:1 | 严重不足,几乎不可读 |
#e5e7eb 文字 + #1f2937 背景 | 11.6:1 | ✓ 达 AAA |
建议暗色模式下:
- 文字色至少使用
#e5e7eb以上亮度(L > 0.7) - 次要文字色至少
#d1d5db(L > 0.6) - 背景色不超过
#1f2937(L ≈ 0.03) - 避免在中灰背景
#374151上放任何文字
渐变与图片背景的处理
WCAG 对渐变与图片背景没有精确公式,建议做法:
- 渐变背景:在渐变最浅和最两端分别测文字对比度,取较低值作为合规判定。如果一端不达标则整体不达标。
- 图片背景:在图片上叠加半透明遮罩(如
rgba(0,0,0,0.5))后再测文字对比度。遮罩颜色应与文字色形成高对比(深色遮罩 + 浅色文字)。 - 复杂图片背景:若无法保证一致对比度,应改为纯色卡片背景文字区,把文字放在不透明卡片上。
.hero-overlay {
position: relative;
}
.hero-overlay::before {
content: '';
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.55); /* 遮罩提升对比度 */
}
.hero-overlay h1 {
position: relative;
color: #ffffff; /* 白字 + 半透明黑遮罩 = 高对比 */
}
工具矩阵:颜色相关工具联动
本站颜色相关工具:
典型工作流:
- 用颜色格式转换选品牌色(拾色器 + 配色方案)
- 把候选前景色与背景色复制到颜色对比度检查验证是否达标
- 不达标时用方案 1(加深文字色)调整,回到第 1 步转换为新 HEX
- 最终在 对比度检查工具 确认所有 5 项评级通过 AA
小结
WCAG 颜色对比度不是「设计师的洁癖」,而是真实用户能否阅读你的界面的硬指标。记住几个关键数字:
- 4.5:1:普通文字 AA(行业基准)
- 3.0:1:大文字 AA + UI 组件 AA
- 7.0:1:普通文字 AAA(增强)
品牌色不达标时优先加深文字色变体,原色保留给大面积色块。暗色模式下不要假设天然高对比,同样需要逐一验证。本站的 对比度检查工具 完全使用 WCAG 官方公式,结果与 W3C 计算器一致,可在开发流程中随时调用。