颜色对比度检测器
检测前景色与背景色之间的WCAG AA和AAA对比度
前景色(文本颜色)
背景色
大号文本示例(18px 粗体)
普通文本示例——这就是你的正文在所选背景色上的实际显示效果。
对比度
14.63:1
WCAG 合规性
通过
普通文本 AA
通过
普通文本 AAA
通过
大号文本 AA
通过
大号文本 AAA
WCAG AA — 普通文本: 4.5:1, 大号文本(18px 及以上或 14px 粗体): 3:1
WCAG AAA — 普通文本: 7:1, 大号文本(18px 及以上或 14px 粗体): 4.5:1
什么是颜色对比度检测?
颜色对比度检测通过计算前景色(通常为文本)与背景色之间的亮度差,并将结果表示为比率。1:1表示两种颜色完全相同;21:1为最大值,即黑色与白色之间的对比度。W3C发布的《网页内容无障碍指南》(WCAG)规定了文本必须满足的最低对比度要求,以确保低视力或色觉障碍用户能够正常阅读。
对比度计算公式来自WCAG 2.x,基于相对亮度——衡量颜色在人眼中呈现亮度的指标。计算相对亮度时,需先对每个sRGB通道进行线性化处理(移除伽马校正),再按ITU-R BT.709系数加权求和:红色0.2126、绿色0.7152、蓝色0.0722。绿色权重最大,因为人眼对绿光最为敏感。最终对比度公式为 (L1 + 0.05) / (L2 + 0.05),其中L1为较亮颜色的亮度值。
WCAG定义了两个合规级别。AA级要求普通文本的对比度至少为4.5:1,大号文本(18px及以上,或14px粗体)至少为3:1。AAA级将标准提高至7:1和4.5:1。WCAG 2.1还新增了成功准则1.4.11,要求边框、图标和焦点指示器等非文本UI组件的对比度不低于3:1。
为何使用此对比度检测器?
凭肉眼判断对比度并不可靠。在你精心校准的显示器上看起来清晰可辨的颜色组合,在低质量笔记本屏幕、强烈日光下,或对色觉缺陷用户而言,可能完全无法区分。数值化的对比度比率消除了主观判断,并以通过/未通过的形式给出明确的WCAG合规结论。
对比度检测器使用场景
WCAG 对比度要求参考
下表汇总了WCAG 2.1针对不同内容类型和合规级别所要求的最低对比度比率。大号文本定义为:正常字重下18px(24 CSS像素)及以上,或粗体字重下14px(18.66 CSS像素)及以上。
| 级别 | 最低比率 | 适用范围 | 备注 |
|---|---|---|---|
| AA Normal text | 4.5 : 1 | Body text, paragraphs, labels | Minimum for most UI text |
| AA Large text | 3.0 : 1 | Text >= 18px, or >= 14px bold | Minimum for headings |
| AA UI components | 3.0 : 1 | Borders, icons, focus indicators | Non-text contrast (WCAG 1.4.11) |
| AAA Normal text | 7.0 : 1 | Body text at highest standard | Enhanced readability |
| AAA Large text | 4.5 : 1 | Large text at highest standard | Enhanced for headings |
相对亮度、AA 与 AAA 详解
对比度计算分三个阶段:分别计算每种颜色的亮度,推导出比率,再与WCAG阈值进行比较。
代码示例
通过编程方式计算WCAG对比度比率。以下示例均实现了WCAG 2.x的相对亮度公式和对比度计算逻辑。为便于对比,所有示例均测试黑色与白色、靛蓝色与白色这两对颜色组合。
// Calculate relative luminance per WCAG 2.x (sRGB)
function luminance(r, g, b) {
const [rs, gs, bs] = [r, g, b].map(c => {
c /= 255
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
})
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs
}
// Contrast ratio between two RGB colors
function contrastRatio(fg, bg) {
const l1 = luminance(...fg)
const l2 = luminance(...bg)
const lighter = Math.max(l1, l2)
const darker = Math.min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
}
contrastRatio([0, 0, 0], [255, 255, 255]) // -> 21.0
contrastRatio([99, 102, 241], [255, 255, 255]) // -> 3.95
contrastRatio([29, 78, 216], [255, 255, 255]) // -> 6.06 (AA pass)def luminance(r: int, g: int, b: int) -> float:
"""Relative luminance per WCAG 2.x, ITU-R BT.709 coefficients."""
channels = []
for c in (r, g, b):
c /= 255
channels.append(c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4)
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2]
def contrast_ratio(fg: tuple, bg: tuple) -> float:
l1 = luminance(*fg)
l2 = luminance(*bg)
lighter, darker = max(l1, l2), min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
print(f"{contrast_ratio((0, 0, 0), (255, 255, 255)):.2f}") # -> 21.00
print(f"{contrast_ratio((99, 102, 241), (255, 255, 255)):.2f}") # -> 3.95
# Check WCAG AA for normal text
ratio = contrast_ratio((29, 78, 216), (255, 255, 255))
print(f"{ratio:.2f} — {'AA pass' if ratio >= 4.5 else 'AA fail'}") # -> 6.06 — AA passpackage main
import (
"fmt"
"math"
)
func linearize(c float64) float64 {
c /= 255
if c <= 0.04045 {
return c / 12.92
}
return math.Pow((c+0.055)/1.055, 2.4)
}
func luminance(r, g, b int) float64 {
return 0.2126*linearize(float64(r)) +
0.7152*linearize(float64(g)) +
0.0722*linearize(float64(b))
}
func contrastRatio(fgR, fgG, fgB, bgR, bgG, bgB int) float64 {
l1 := luminance(fgR, fgG, fgB)
l2 := luminance(bgR, bgG, bgB)
lighter := math.Max(l1, l2)
darker := math.Min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
}
func main() {
ratio := contrastRatio(0, 0, 0, 255, 255, 255)
fmt.Printf("%.2f\n", ratio) // -> 21.00
ratio = contrastRatio(29, 78, 216, 255, 255, 255)
fmt.Printf("%.2f\n", ratio) // -> 6.06 (AA pass for normal text)
}/* WCAG-safe color pairs — tested contrast ratios */
/* 12.63:1 — passes AAA normal text */
.high-contrast {
color: #1e293b; /* slate-800 */
background: #f8fafc; /* slate-50 */
}
/* 7.07:1 — passes AAA normal text */
.dark-theme-text {
color: #e2e8f0; /* slate-200 */
background: #0f172a; /* slate-900 */
}
/* 4.57:1 — passes AA normal, fails AAA */
.accent-on-white {
color: #1d4ed8; /* blue-700 */
background: #ffffff;
}
/* 2.14:1 — fails AA for text, but passes 3:1 for large text */
.muted-heading {
color: #94a3b8; /* slate-400 */
background: #ffffff;
}