freeCodeCamp 实战:用 WCAG 对比度标准修复低对比度文字,让网页内容真正可读
本篇指南基于 freeCodeCamp 响应式设计课程(Responsive Web Design)中 Applied Accessibility 板块的第 16 关 “Improve Readability with High Contrast Text”。你将理解 WCAG 推荐的 4.5:1 文本对比度标准背后的相对亮度(relative luminance)计算原理,学会修改 CSS color 属性将 1.5:1 的低对比度配色提升到 6:1,并了解本仓库中该题目的验证逻辑与后续进阶挑战的完整脉络。
主题背景:它为什么重要
前景色与背景色之间的对比度过低,会让文字变得难以辨认。对比度足够,内容的可读性才会真正提高——但“足够”到底是多少?
《Web 内容无障碍指南》(Web Content Accessibility Guidelines, WCAG)对正常字号文本推荐至少 4.5:1 的对比度。该比值通过比较两种颜色的相对亮度(relative luminance)计算得出,取值范围是:
- 1:1 —— 同一颜色,即完全没有对比;
- 21:1 —— 白底黑字,最大对比度。
由于手工计算相对亮度涉及伽马校正和加色模型,实际开发中通常会使用在线的对比度检测工具来自动算出这个比值。本仓库中另有专门的教学内容演示了这一计算过程,后文会给出参考。
这道题目位于课程结构文件 curriculum/structure/blocks/applied-accessibility.json 所列的 21 个 Applied Accessibility 挑战中的第 16 位(id: 587d778e367417b2b2512aab),属于 curriculum/structure/superblocks/responsive-web-design.json 定义的大板块之一。整个 Applied Accessibility 板块从图片替代文本、标题层级、landmark 元素讲到颜色对比与链接可访问性,本题正是“视觉无障碍”部分(灰度对比)的起点。
练习场景:Camper Cat 的浅灰博客文字
题目给出的初始页面(seed 代码)中,Camper Cat 的博客用了浅灰文字配白色背景:
<head>
<style>
body {
color: #D3D3D3;
background-color: #FFF;
}
</style>
</head>
<body>
<header>
<h1>Deep Thoughts with Master Camper Cat</h1>
</header>
<article>
<h2>A Word on the Recent Catnip Doping Scandal</h2>
<p>The influence that catnip has on feline behavior is well-documented, and its use as an herbal supplement in competitive ninja circles remains controversial. Once again, the debate to ban the substance is brought to the public's attention after the high-profile win of Kittytron, a long-time proponent and user of the green stuff, at the Claw of Fury tournament.</p>
<p>As I've stated in the past, I firmly believe a true ninja's skills must come from within, with no external influences. My own catnip use shall continue as purely recreational.</p>
</article>
</body>
这组配色的对比度只有 1.5:1,远低于 4.5:1 的 WCAG 推荐值,读起来非常吃力。题目要求把 body 的 color 从浅灰 #D3D3D3 改为更深的灰色 #636363,使对比度提升到 6:1。
修改方法:只动 color,不动背景
完整的解决方案只需改动一处:
<head>
<style>
body {
color: #636363;
background-color: #FFF;
}
</style>
</head>
这里的关键操作是把 #D3D3D3 替换为 #636363,而 background-color: #FFF; 保持原样。
#636363 换算成十进制正是 RGB (99, 99, 99),浏览器计算样式(computed style)中会显示为 rgb(99, 99, 99)。这一点恰好对应题目内置的验证逻辑,即题目 frontmatter 与 --hints-- 部分给出的两段断言:
// 验证 1:body 的文字颜色必须是深灰 rgb(99, 99, 99),即 #636363
const body = document.querySelector('body');
const bodyColor = window.getComputedStyle(body).color;
assert(bodyColor == 'rgb(99, 99, 99)');
// 验证 2:body 的背景色必须保持白色 rgb(255, 255, 255),不能顺手改动
const body = document.querySelector('body');
const backgroundColor = window.getComputedStyle(body).backgroundColor;
assert.equal(backgroundColor , 'rgb(255, 255, 255)');
从源码结构看,这两段断言说明本关考察的是**“最小化修改”**:只允许调整文字颜色,背景色一旦改变(哪怕也提升了可读性)同样会判错。原始题目文件位于 curriculum/challenges/english/blocks/applied-accessibility/587d778e367417b2b2512aab.md,其中 --hints--、--seed--、--solutions-- 三段分别对应“验证条件 / 初始代码 / 标准答案”。
原理解深:对比度到底是怎么算出来的
题目描述提到“比值通过比较两种颜色的相对亮度计算”,但没有展开公式。本仓库的 Daily Coding Challenge(JavaScript 版)中有一组题目完整实现了 WCAG 的相对亮度算法,可作为原理印证。
Challenge 354 给出的标准计算步骤是:
- 将 RGB 每个通道除以 255,归一化到
0–1; - 对每个通道做伽马校正:
- 若通道值
<= 0.04045:channel / 12.92 - 否则:
((channel + 0.055) / 1.055) ^ 2.4
- 若通道值
- 加权求和得到相对亮度:
0.2126 * R + 0.7152 * G + 0.0722 * B(绿色权重最高,对应人眼对绿光最敏感) - 对比度 =
(较亮亮度 + 0.05) / (较暗亮度 + 0.05)
对应的参考实现(来自该题目的 --solutions--):
function toLuminance([r, g, b]) {
return [r, g, b].map(channel => {
channel = channel / 255;
return channel <= 0.04045
? channel / 12.92
: ((channel + 0.055) / 1.055) ** 2.4;
}).reduce((sum, c, i) => sum + c * [0.2126, 0.7152, 0.0722][i], 0);
}
const l1 = toLuminance(rgb1); // 较亮颜色的相对亮度
const l2 = toLuminance(rgb2); // 较暗颜色的相对亮度
const ratio = (l1 + 0.05) / (l2 + 0.05);
姊妹题 Challenge 353 则展示了拿到比值之后如何评级:
| 评级 | 正常文本 | 大文本 |
|---|---|---|
"AAA" |
7.0+ | 4.5+ |
"AA" |
4.5+ | 3.0+ |
"Fail" |
低于 4.5 | 低于 3.0 |
由此可以对号入座:
- 本题 seed 配色
#D3D3D3on#FFF≈ 1.5:1,不达标; - 修改后的
#636363on#FFF≈ 6:1,达到“AA”甚至接近“AAA”(7:1)水平; - 本文档强调的 4.5:1 正是上表中正常文本达到 AA 的下限,而 21:1(黑白)就是理论上界。
进阶衔接:用 HSL 调节彩色的对比度
本关处理的是灰度场景。紧随其后的下一关 “Avoid Colorblindness Issues by Using Sufficient Contrast”(curriculum/challenges/english/blocks/applied-accessibility/587d778f367417b2b2512aac.md)把同一套标准扩展到彩色场景,值得提前了解其方法:
- 4.5:1 的标准对彩色与灰度组合同样适用;
- 用
hsl(hue, saturation%, lightness%)声明颜色时,只改第三个参数 lightness 即可调节明暗:把较暗的颜色 shading(加黑)、较亮的颜色 tinting(加白); - 该关的具体操作是把
background-color的 lightness 从 35% 提到 55%、color的 lightness 从 20% 降到 15%,对比度从 2.5:1 提升到 5.9:1。
再往后一关则会处理“黄底绿字”这类色盲友好的色相选择问题,整个三连构成了“灰度对比 → 彩色明度 → 色相选择”的完整递进。
小结与可复用的检查清单
把这道 Applied Accessibility 题目拆解开来,落到工程实践就是一条可复制的检查清单:
- 先测量:对正文的主文字色与背景色,用对比度工具(或在 DevTools 中查看)算出当前比值;
- 对照标准:正常文本 < 4.5:1 即需要整改,追求更高可读性可逼近 7:1;
- 最小化改动:像本题一样只改
color(或只调 HSL 的 lightness),保留background-color等其他声明不动,既保证验证通过,也避免破坏设计系统的整体基调; - 用计算样式验证:
window.getComputedStyle(el).color返回的是rgb()形式,十六进制#636363对应rgb(99, 99, 99),排查时注意两种表示的换算。
参考文件:题目原文见 587d778e367417b2b2512aab.md,板块顺序见 applied-accessibility.json,亮度计算实现见 Challenge 353 与 Challenge 354。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00