颜色对比度检测

算出一对前景色与背景色的 WCAG 对比度比值,按 AA 4.5:1、AA 大文本 3:1、AAA 7:1 三档给判定,并告诉你这个颜色只要把明度调到哪一档就刚好达标。

示例文字:#767676 配 #FFFFFF,16px 常规体,对比度 4.54:1。

白色背景上的灰阶梯度:共 25 档,当前显示 12 档。

档位灰阶相对亮度 L与白底对比度AA 4.5:1AA 大文本 3:1AAA 7:1
1#0000000.000021.00:1通过通过通过
2#0B0B0B0.003319.68:1通过通过通过
3#1515150.007518.26:1通过通过通过
4#2020200.014416.29:1通过通过通过
5#2B2B2B0.024214.16:1通过通过通过
6#3535350.035612.27:1通过通过通过
7#4040400.051310.37:1通过通过通过
8#4A4A4A0.06858.86:1通过通过通过
9#5555550.09087.46:1通过通过通过
10#6060600.11706.29:1通过通过未通过
11#6A6A6A0.14415.41:1通过通过未通过
12#7575750.17794.61:1通过通过未通过

提示:在 HEX 文本框或字号框里按 Enter 可立即重算;打开页面时前景色是 #767676、背景色是 #FFFFFF,与下方「完整示例」和灰阶对照表用的是同一组值。

使用方法

  1. 页面打开时已经是一组现成的示例:前景色(文字色)#767676、背景色 #FFFFFF、字号 16 px、不加粗。结果区显示的 4.54:1、下面「完整示例」一节和灰阶对照表用的是同一组数字,可以直接对照着看。
  2. 换前景色有两条路:拖动拾色器,或者在它右边的文本框里键入六位十六进制值(#767676 或 767676 都认,三位缩写 #666 不认)。背景色同理。任意一处改动,拾色器和文本框立刻同步,比值同步重算。
  3. 填字号:这个数字只做一件事——判断你的文字算不算 WCAG 定义的「大文本」。填 16 表示 16 CSS px,允许 8~96 的整数。是否加粗用旁边的复选框表示。
  4. 看结果区四行:第一行是比值(保留两位小数),第二行三枚标签是 AA 4.5:1 / AA 大文本 3:1 / AAA 7:1 的通过情况,第三行说明按你当前字号实际适用的门槛,第四行给出「保持色相、只调明度」后刚好达标的前景色,并附一个色块供你直接比色。
  5. 点「交换前景/背景」可以把两个颜色对调,验证结论是否对称;点「载入示例」还原默认值;点「复制结果」把比值、三档判定和达标色一次性带走,粘进设计评审意见或工单里。
  6. 下方的灰阶表默认显示 12 档,「加载更多」补齐 25 档,「复制对照表」按制表符复制,「导出 CSV」导出带 BOM 的表格文件,用 Excel 打开不会乱码。

颜色对比度检测能解决什么问题

一是上线前把「看不清」这件事量化。设计稿里灰字放在白底上,肉眼只能说「有点浅」;把两个色值填进来,得到的是 4.54:1 这样一个可比较、可交付的数字,够不够、差多少一目了然。

二是反向求解,避免过度保守。很多人被要求「加深一点」之后直接把 #767676 改成纯黑,结果整页对比度冲到 21:1,界面显得很硬。这个颜色对比度检测会告诉你,要达到 AAA 只需要降到 #595959(7.00:1),要保住 AA 大文本档只需要不浅于 #949494(3.03:1),改动幅度可以精确到一档。

三是判断「大文本豁免」到底适不适用。WCAG 对大号文字放宽到 3:1,但「大号」有明确门槛:18pt 及以上,或者 14pt 及以上且加粗。换算成 CSS 像素就是 24px,或者 18.66px 且加粗。把字号填进来,页面会直接告诉你是否踩进豁免档,省得拿 20px 的非粗体正文去套 3:1。

四是给整改留一条可执行路径。无障碍验收意见常常只写「对比度不足」,开发拿到之后仍然不知道该改成什么色。把前景色填成现在用的值,结果区第四行给的就是可以直接替换的六位色值,而且是保持色相与饱和度、只压明度的最小改动。

这个颜色对比度检测不做这些事:不做色盲类型模拟(请用页面底部的「色盲模拟」)、不做全项无障碍审计(WCAG 涉及上百条成功准则,本页只覆盖对比度相关的 1.4.3、1.4.6 和 1.4.11 三条)、也不判断颜色本身好不好看。它只回答一个问题:这一对颜色,按 WCAG 2.1 的相对亮度公式算出来是多少比一,以及够不够用。

原理和变量:WCAG 2.1 相对亮度与对比度公式

对比度不是「两个颜色差多远」的直觉量,而是一个有固定公式的比例,出自 W3C 的 WCAG 2.1(Web Content Accessibility Guidelines),由成功准则 1.4.3 引用。计算分四步,每一步都能在页面里复现。

第一步,把 8 位 sRGB 分量归一化到 0~1。十六进制色值 #767676 拆成 R=0x76=118、G=118、B=118,各自除以 255 得到 0.4627。这一步没有取舍空间,纯粹是取值范围换算。

第二步,做 sRGB 的 gamma 反变换(线性化)。显示器上的亮度不是按 sRGB 数值线性增长的,必须先把编码值还原成线性光。WCAG 2.1 给出的分段函数是:当分量 c ≤ 0.03928 时用 c / 12.92,否则用 ((c + 0.055) / 1.055)2.4。以 0.4627 代入,得到 ((0.4627 + 0.055) / 1.055)2.4 = 0.181164。这一步是整条链路里最容易被跳过的一步,跳过它就意味着你在用 gamma 编码值冒充亮度。

第三步,按人眼感光特性加权求和。相对亮度 L = 0.2126R + 0.7152G + 0.0722B,这里的 R、G、B 是上一步线性化之后的值。三个系数不是随便凑的,它们来自 CIE 1931 色度系统里 XYZ 的 Y 分量在 sRGB(ITU-R BT.709 原色、D65 白点)下的取值,含义是:同样能量的绿光看起来比红光亮、红光又比蓝光亮。示例色三通道相同,所以 L = (0.2126 + 0.7152 + 0.0722) × 0.181164 = 1.0000 × 0.181164 = 0.181164,恰好等于单通道的线性值。

第四步,代入比值公式。对比度 = (L1 + 0.05) / (L2 + 0.05),其中 L1 是较亮那个颜色的相对亮度、L2 是较暗的那个。分母里的 0.05 不是装饰:它模拟的是真实观看环境里的环境光与屏幕反光——纯黑屏幕上多少会映出一点环境光,所以「黑与黑」的比值被压到 1:1,而「黑与白」的最大值也只有 21:1,不是 255:1。示例中背景 #FFFFFF 的 L 是 1.000000,于是 (1.000000 + 0.05) / (0.181164 + 0.05) = 1.05 / 0.231164 = 4.5422。

为什么门槛是 4.5、3、7 这三个数。WCAG 2.1 的规定是:普通文本达到 AA 需要 4.5:1,达到 AAA 需要 7:1;大文本(18pt 及以上,或 14pt 及以上且加粗)AA 放宽到 3:1,AAA 放宽到 4.5:1。三个阈值不是连续可调的参数,是标准里的固定档位,本页照抄不改。

0.03928 与 0.04045 的分歧。现行的 sRGB 规范(IEC 61966-2-1)把线性化的分界点写成了 0.04045,后来还出现过 0.0031308 的写法;WCAG 2.1 沿用的是更早一版的 0.03928。两个门槛之间只有 0.00117 的差距,落在这一小段上的分量极少,对最终比值的影响通常在小数点后第三位。本页按 WCAG 2.1 的 0.03928 计算,因为要判定的是 WCAG 的条款;如果你是在做色彩科学计算而不是无障碍判定,应该用 0.04045。

「刚好达标的颜色」是怎么算出来的。做法是固定前景色的色相 H 和饱和度 S(HSL 口径),只让明度 L 从 0 到 1 以 0.0005 为步长扫 2001 次,每次把结果量化成 8 位十六进制再去算真实比值,最后取「满足目标阈值、且明度与原色差别最小」的那一档。之所以敢用扫描,是因为在 H、S 固定时,sRGB 三个通道随 HSL 明度单调不降,相对亮度也随之单调不减,所以达标区间一定是连续的单边区间,扫出来的解就是 8 位色里刚好越过门槛的那一档,不会漏掉更接近原色的选择。

完整示例

示例条件与页面打开时的默认值完全一致:前景色 #767676、背景色 #FFFFFF、字号 16px、不加粗。先把中间量列清楚,再给结论。

三档判定:AA 普通文本 4.5:1 通过(余量只有 0.0422)、AA 大文本 3:1 通过、AAA 7:1 未通过。这就是 #767676 被大量设计系统当作「白色背景上最浅的可用正文灰」的原因——它几乎正好压在 4.5:1 这条线上,再浅一步就掉出去。

把明度往下压,看两档的达标色(色相、饱和度都不动):

目标达标前景色实际对比度与原色相比
AA 大文本 3:1#9494943.03:1变浅;原色 #767676 已远超这一档,不需要改
AA 普通文本 4.5:1#7676764.54:1就是原色本身,刚好越过门槛
AAA 7:1#5959597.00:1明度从 0.463 压到 0.351,暗了一档半

再看一眼边界的另一边:把前景色换成 #777777,比值降到 4.4780,显示为 4.48:1,AA 普通文本立刻变成未通过。#767676 与 #777777 在 8 位色里只差 1 个灰阶(118 与 119),比值却从 4.5422 跳到 4.4780。这也是为什么本页把「白底最浅可用的正文灰」精确定位在 #767676 而不是笼统地说「666 附近的灰」。

输入范围与对照表

本页接受的输入范围以及越界时的处理:

下面这张表把白色背景上等间距取的 25 档灰阶逐档列出。注意取样步长是 255 / 24 ≈ 10.625 再四舍五入,所以它是一张粗网格,真正的边界值 #767676 落在第 11 档(#757575,4.61:1)与第 12 档(#808080,3.95:1)之间。表格默认显示 12 档。

同时给三个「最浅门槛」的精确值,它们都不在粗网格上:白底灰字要过 AA 4.5:1,最浅只能用 #767676(4.542);要过 AA 大文本 3:1,最浅可用 #949494(3.033);要过 AAA 7:1,最浅只能用 #595959(7.005)。再浅一个灰阶,三者分别掉到 4.478、2.995、6.897。

下表是几组常见前台色与纯白背景的实测值,可以直接拿来核对设计系统里的灰阶与品牌色:

前景色所属相对亮度 L与白底对比度AA 4.5:1AAA 7:1
#111827gray-9000.009217.74:1通过通过
#1F2937gray-8000.021514.68:1通过通过
#374151gray-7000.051910.31:1通过通过
#4B5563gray-6000.08897.56:1通过通过
#6B7280gray-5000.16724.83:1通过未通过
#2563EBblue-6000.15325.17:1通过未通过
#3B82F6blue-5000.23553.68:1未通过未通过
#DC2626red-6000.16744.83:1通过未通过
#16A34Agreen-6000.26863.30:1未通过未通过
#F59E0Bamber-5000.43892.15:1未通过未通过

「大文本」的两条门槛换算成像素的精确值是:18pt = 24 CSS px;14pt = 18.6667 CSS px(必须同时加粗)。pt 与 px 的换算是 px = pt × 96 / 72,其中 96 是 CSS 规范里 1 英寸对应的像素数。因为字号输入是整数,本页把第二条判定为「加粗且字号 ≥ 19」,也就是不满足 18.6667 的整数一律不放行。

结果边界与舍入误差

这个颜色对比度检测在几个地方刻意做了取舍,知道边界在哪,才不会把结论用错地方。

常见问题

为什么 #767676 显示 4.54 就算通过,而 #777777 的 4.48 不通过?

因为门槛是 4.5:1 这条硬线,不是「接近 4.5 就行」。#767676 的真实比值是 4.5422,大于 4.5,通过;#777777 是 4.4780,小于 4.5,不通过。两个颜色在 8 位色里只差 1 个灰阶,但这条线上没有缓冲区。如果你的设计对余量敏感,建议留到 4.6:1 以上再交付,免得后续微调色彩时掉线。

WCAG 2.1 用 0.03928,现代 sRGB 规范用 0.04045,我该信哪个?

判定 WCAG 合规就用 0.03928,因为 WCAG 2.1 的「相对亮度」定义就写的是这个值,本页也按它算;做色彩科学、色彩空间转换一类的计算就用 0.04045,那是 IEC 61966-2-1 现行版本的写法。两者相差 0.00117,只影响归一化后落在这一段的分量,对最终比值的影响通常在小数点后第三位——但它是一个真实存在的分歧,两个工具算出 4.54 和 4.55 时不必惊慌。

「大文本」到底怎么算?为什么门槛是 18pt 而不是 24px?

WCAG 2.1 的定义用的是印刷单位:18pt 及以上,或者 14pt 及以上且加粗。CSS 里 1pt = 96/72 px,所以 18pt 恰好等于 24px,14pt 约等于 18.6667px。于是 24px 及以上的任意字重、或者 19px 及以上的加粗文字,都算大文本,AA 门槛从 4.5:1 放宽到 3:1。注意「19px 及以上」是本页对整数输入的处理:18px 加粗按定义不达标,必须到 19px。

对比度是不是越高越好?做到 21:1 有没有副作用?

数值上越高越容易看清,但文字与背景的对比度到某个程度之后,再加大的收益会递减,代价却很实在:纯黑文字配纯白背景(21:1)在夜间或低亮度屏幕上会产生明显的光晕感,长文阅读体验反而变差。很多现代设计系统把正文对比度控制在 7:1 到 12:1 之间,就是折中结果。AAA 的 7:1 是一个「足够好」的档位,不是起点。

为什么黑白之间最大只有 21:1,不是 255:1?

因为公式的分母和分子都加了 0.05。纯白的相对亮度是 1,纯黑是 0,代入得到 (1 + 0.05) / (0 + 0.05) = 21。这个 0.05 是标准里用来模拟环境光和屏幕反射的常数,它的存在让「全黑」不可能真的没有任何亮度,也让整条比值曲线被压缩。所以 21:1 是这个公式定义下的绝对上限,任何颜色组合都超不过它。

这个比值能换算成 ΔE 色差,或者亮度差吗?

不能直接换算。对比度只看两个颜色的相对亮度(也就是感知亮度),完全不关心色相与饱和度;ΔE 是 CIELAB 空间里的欧氏距离,同时包含亮度差和色度差。两者衡量的是不同的东西:两个色相差别很大但亮度相同的颜色,ΔE 很高,对比度却是 1:1。把这两类指标放在一起平均或互相代入,得到的数字没有意义。

为什么这一页只覆盖对比度,不顺便查一遍色盲?

因为这是两件事。对比度检测问的是「亮不亮得起来」,色盲模拟问的是「不同色盲类型下这两种颜色还分不分得开」——一对颜色可以对比度很高却让红绿色盲完全无法区分(例如红色文字配绿色背景之外的许多组合)。WCAG 里对应的成功准则也不是同一条。想做色盲类型下的视觉模拟,请用页面底部的「色盲模拟」。

结果区给的「达标色」为什么只改明度,不动色相和饱和度?

因为改色相或降饱和度会改变品牌色的观感,而多数团队的诉求是「保持这个蓝/这个灰的调子,只把它压深一点」。工具在 HSL 口径下固定 H 与 S、只扫明度,得到的解在 8 位色里是刚好越过门槛的那一档,改动幅度最小。如果你本来就可以换色,那用「互补色颜色查询」或「调色工具」重新挑一个色反而更省事。

相关工具