我把这个站点的配色算了一遍,然后改了两处

写主题的时候,次要文字(卡片摘要、日期、页脚)我随手挑了个灰:#8b95a1。看着挺舒服,14.5px,和白底的关系也算柔和。

然后我把它代进 WCAG 的对比度公式算了一下:3.04 : 1。小字要求是 4.5 : 1。不合格。

公式不长,自己算比猜靠谱

def lum(hex_):
    vs = [int(hex_.lstrip('#')[i:i+2], 16) / 255 for i in (0, 2, 4)]
    f  = lambda c: c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
    r, g, b = map(f, vs)
    return 0.2126 * r + 0.7152 * g + 0.0722 * b

def contrast(a, b):
    la, lb = sorted((lum(a), lum(b)), reverse=True)
    return (la + 0.05) / (lb + 0.05)

三段:sRGB 归一化 → 线性化(分段函数,低频段是线性的)→ 按人眼敏感度加权(绿最高 0.7152,蓝最低 0.0722)。最后 (亮 + 0.05) / (暗 + 0.05)——加 0.05 是为了不让纯黑除以纯黑炸掉。

实测数字(真跑出来的,不是估计)

前景 背景 比值 AA 正文 4.5
#a5aeb8(卡片日期原色) #ffffff 2.25 : 1 ✗
#8b95a1(次要文字原色) #ffffff 3.04 : 1 ✗
#8b95a1 #f6f7f9(页面底色) 2.83 : 1 ✗
#767f8a #ffffff 4.06 : 1 ✗ 差一点
#6b7280 #ffffff 4.83 : 1 ✓
#667085 #ffffff 4.97 : 1 ✓
#667085 #f6f7f9 4.64 : 1 ✓
#0a84ff(系统蓝) #ffffff 3.65 : 1 ✗
#2563eb(现在用的链接色) #ffffff 5.17 : 1 ✓
#e6edf3(代码块正文) #0d1117 16.02 : 1 ✓
#ffffff #0d1117 18.92 : 1 ✓

三个结论,两处改动

一、同一色系里,”浅一点点”经常就直接掉下去了。 #767f8a 是 4.06,#6b7280 是 4.83。肉眼几乎分不出,规范上一个不合格一个合格。这类边界必须靠算,手感在这里没有分辨率。

二、”系统蓝”不能直接拿来当正文链接色。 #0a84ff 是 3.65 : 1。它做大按钮的底色、做图标都很好,但当 14–16px 的文字颜色就不合格。#2563eb 5.17,同样一眼蓝,能用。

三、改了变量,别只改一处。 我原来是 --c-muted 一个变量管所有次要文字,但卡片日期那行单独写死了 #a5aeb8(2.25 : 1,最差的一处)。所以两处一起改:变量 #8b95a1 → #667085,卡片日期改成引用同一个变量。

关于代码块:为什么不用纯白配纯黑

#ffffff 配 #0d1117 是 18.92 : 1,比现在用的 16.02 更”清楚”。但长段代码里,过高的对比会让眼睛更快疲劳——文字边缘的极端反差在长时间阅读时是负担。

16 : 1 已经远超 AA(4.5)和 AAA(7)的要求,留出来的那点余量是给”连续看二十分钟”准备的。这个取舍没有客观标准,我承认是主观的。

但有一件事不主观:先算,再取舍。凭感觉挑个灰,和算完之后决定”这里我故意用 4.0,因为它是纯装饰不承载信息”——这两件事看起来结果一样,实际上只有后者知道自己冒了什么风险。

一句给自己留的话

AA 的 4.5 是下限,不是目标。小字号(13–14px)我以后尽量做到 5 : 1 以上,因为抗锯齿和屏幕密度会让实际观感比公式算的更差——公式里没有这两样东西。

← 返回技术笔记