freeCodeCamp 每日挑战实战:用 JavaScript 实现 WCAG 色彩对比度评级(Challenge 353: Contrast Rating 2)
freeCodeCamp 每日挑战实战用 JavaScript 实现 WCAG 色彩对比度评级Challenge 353: Contrast Rating 2【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp本篇围绕 freeCodeCamp 每日编程挑战Daily Coding Challenge中的第 353 题「Contrast Rating 2」展开给定两个相对亮度值和一个是否大字布尔值按 WCAGWeb 内容无障碍指南的对比度公式计算对比率并按 AAA / AA / Fail 阈值表输出评级。读完本文你将理解 WCAG 对比率公式中0.05的由来、评级阈值表的分档逻辑并能独立完成从公式推导、边界判断到参考实现的全部过程同时了解这道题在 freeCodeCamp 课程库中的定位与相邻挑战的递进关系。挑战定位daily-coding-challenges-javascript 系列中的第 353 题该挑战的定义文件位于 curriculum/challenges/english/blocks/daily-coding-challenges-javascript/6a22d77ddf034bc4e35b1d5a.mdfront-matter 声明如下--- id: 6a22d77ddf034bc4e35b1d5a title: Challenge 353: Contrast Rating 2 challengeType: 28 dashedName: challenge-353 ---challengeType: 28在 freeCodeCamp 的课程类型常量中被定义为dailyChallengeJs。可以在 packages/shared/src/config/challenge-types.ts 中看到定义以及该类型在视图与提交方式上的映射viewTypes为classic经典编辑器视图submitTypes为tests即以测试断言判定是否通过const dailyChallengeJs 28; // L30 // ... [dailyChallengeJs]: classic, // viewTypes经典编辑器界面 [dailyChallengeJs]: tests, // submitTypes通过断言测试判分在课程结构文件 curriculum/structure/blocks/daily-coding-challenges-javascript.json 的challengeOrder数组中它排在全系列 365 道题的第 353 位且恰好处于一个由三题组成的无障碍对比度递进小节的中间序号标题输入考察重点Challenge 352Contrast Rating 1已算好的对比率字符串阈值分档判断Challenge 353本文Contrast Rating 2两个相对亮度值对比率公式 阈值分档判断Challenge 354Contrast Rating 3两组 RGB 数组RGB→相对亮度伽马校正 前两步从源码结构看三题是刻意设计的阶梯352 只练判断分支353 加入对比率公式354 再补上最底层的 RGB 到相对亮度换算。本文聚焦 353并会引用相邻两题作为上下文佐证。该 block 的配置还声明了usesMultifileEditor: true与blockLayout: legacy-challenge-list见 block 定义文件每日挑战的选题入库则由 tools/daily-challenges/seed-daily-challenges.ts 这类脚本负责。题目描述从两个相对亮度到 WCAG 评级原题--description--部分的要求完整继承自挑战文件给定两个相对亮度relative luminance值以及一个表示文字是否为大字large text的布尔值按以下方法返回 WCAG 对比度评级对比率的计算方式给两个亮度值各加 0.05然后用较亮的那个除以较暗的那个。第一个参数始终是较亮的一方。再根据对比率查下表返回评级评级普通文字Normal Text大字Large TextAAA7.04.5AA4.53.0Fail低于 4.5低于 3.0公式拆解为什么是 (l1 0.05) / (l2 0.05)WCAG 的对比率定义式为contrastRatio (L1 0.05) / (L2 0.05)其中L1为较亮颜色的相对亮度L2为较暗颜色的相对亮度范围均为 01。这里的 0.05 不是随意取的常数其工程意义是模拟真实显示环境中黑色并不绝对黑、白色并不绝对白——屏幕本身有环境光与亮度底噪。通过给分子分母同时加一个小的常数偏移可以避免纯黑 vs 纯白这类极端组合产生理论上限 21:1 之外的失真同时让靠近黑端的小亮度差异在比值中不至于被过度放大。题目中第一个参数始终是较亮一方这一前提很关键它省去了Math.max/Math.min排序步骤让实现可以无条件地把l1放在分子。这也是 353 相比给定任意两色的通用实现更简洁的原因。阈值表解读四档判断两个分支评级表可以转写为判断逻辑普通文字isLargeText falseratio 7.0为AAAratio 4.5为AA否则Fail。大字isLargeText trueratio 4.5为AAAratio 3.0为AA否则Fail。注意三点大字的所有阈值恰好等于普通文字低一档的阈值因此两套逻辑可以共用同一组数值常量只需按isLargeText选分支边界值取闭区间例如 4.5 的普通文字恰好是AA而不是Fail相对亮度是感知归一化后的值由线性 RGB 经伽马校正与色度加权得到所以对比率是对人眼感知亮度差的度量而非原始颜色数值差。354 题中的 RGB→亮度换算正是产出这两个输入值的前置步骤。六条断言测试用例的逐一验算挑战文件的--hints--给出了 6 条测试断言它们正好两两覆盖普通文字 / 大字 × AAA / AA / Fail的组合。下面把每条用例的对比率手工算一遍验证期望输出的自洽性assert.equal(getContrastRating(1.0, 0.0, false), AAA); assert.equal(getContrastRating(0.9015, 0.1364, false), AA); assert.equal(getContrastRating(0.8965, 0.1628, false), Fail); assert.equal(getContrastRating(0.7469, 0.0957, true), AAA); assert.equal(getContrastRating(0.7489, 0.2018, true), AA); assert.equal(getContrastRating(0.6571, 0.1974, true), Fail);输入 (l1, l2, isLargeText)计算 (l10.05)/(l20.05)结果判定依据(1.0, 0.0, false)1.05 / 0.05 21.00AAA21.0 ≥ 7.0(0.9015, 0.1364, false)0.9515 / 0.1864 ≈5.10AA4.5 ≤ 5.10 7.0(0.8965, 0.1628, false)0.9465 / 0.2128 ≈4.45Fail4.45 4.5(0.7469, 0.0957, true)0.7969 / 0.1457 ≈5.47AAA5.47 ≥ 4.5大字(0.7489, 0.2018, true)0.7989 / 0.2518 ≈3.17AA3.0 ≤ 3.17 4.5大字(0.6571, 0.1974, true)0.7071 / 0.2474 ≈2.86Fail2.86 3.0大字两个值得留意的用例设计第 3 条0.8965 / 0.1628得到的对比率约 4.45只比 4.5 的 AA 门槛低一点点属于典型的临界 Fail用来确认边界判断写的是而不是或与混用第 5 条0.7489 / 0.2018 ≈ 3.17说明大字模式下的 AA 门槛3.0显著更宽同一组亮度若用于普通文字就会直接判 Fail——这正是大字对比要求更宽松这一 WCAG 规则的量化体现。另外亮度值保留了 4 位小数如 0.9015、0.1364说明它们是 354 题中 RGB→亮度管线预先算好再传入的中间结果而非随手编造的数字。种子代码与参考解法挑战的初始骨架--seed--部分故意返回错误结果要求学员补全实现function getContrastRating(l1, l2, isLargeText) { return l1; }挑战文件自带的参考解法--solutions--部分如下function getContrastRating(l1, l2, isLargeText) { const ratio (l1 0.05) / (l2 0.05); if (isLargeText) { if (ratio 4.5) return AAA; if (ratio 3.0) return AA; } else { if (ratio 7.0) return AAA; if (ratio 4.5) return AA; } return Fail; }实现要点逐行解读先算比率再分档ratio只计算一次。若把除法写进每个if条件里代码更啰嗦且在某些写法下比如把l1当普通变量漏加分母容易引入不一致。分支顺序即优先级先判 AAA 再判 AA最后 fall-through 到return Fail。由于 AAA 门槛严格高于 AA 门槛顺序判断保证了满足更高档绝不落到低档。反过来先判 AA 就会把 AAA 的用例误判掉。isLargeText在外层、阈值在内层与阈值表的列普通/大字对应外层分支行AAA/AA/Fail对应内层判断代码结构与表格一一对应便于对照审查。边界写法全部使用与题目表中 7.04.53.0 的闭区间语义一致。第 3 条测试用例ratio ≈ 4.45 → Fail正是对这一点的验证。浮点安全性题目约定l1总是较亮方因此分子恒大于等于分母ratio 1恒成立不存在除零或负值问题分母l2 0.05 0.05 0。测试数据均远离 4.5 / 3.0 的浮点噪声区间直接用比较即可无需额外做精度舍入。一种等价的表驱动写法如果觉得 if 嵌套不够直观可以把阈值表直接数据化按分档从高到低匹配function getContrastRating(l1, l2, isLargeText) { const ratio (l1 0.05) / (l2 0.05); // 每行[AAA 门槛, AA 门槛]按普通/大字选列 const thresholds isLargeText ? { aaa: 4.5, aa: 3.0 } : { aaa: 7.0, aa: 4.5 }; if (ratio thresholds.aaa) return AAA; if (ratio thresholds.aa) return AA; return Fail; }两个分支共享同一个AAA → AA → Fail的判定顺序只是门槛常量不同逻辑更收敛。无论采用哪种写法六条断言的判定结果完全一致。上下文佐证352 与 354 如何与 353 咬合与 352Contrast Rating 1的关系Challenge 352 文件 的输入是已经算好的对比率字符串函数签名为getContrastRating(ratio, isLargeText)阈值表与 353 完全相同。它的参考解法就是 353 解法去掉第一行ratio计算后的部分。也就是说352 练习分档判断353 在此基础上补上如何从亮度算出对比率。与 354Contrast Rating 3的关系Challenge 354 文件 的输入是两组 RGB 数组要求在得到相对亮度后再执行与 353 完全相同的对比率与评级逻辑。其参考解法中包含了 353 省略掉的底层换算可以完整看到 WCAG 亮度管线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); }即通道值除以 255 归一化 → 逐通道伽马校正≤ 0.04045 走线性段否则走 2.4 次幂段→ 按0.2126*R 0.7152*G 0.0722*B的色度系数加权求和。353 题使用的 0.9015、0.1364 这类四舍五入到 4 位的亮度值正是这一管线的典型输出。三题串起来就是一条RGB → 相对亮度 → 对比率 → WCAG 评级的完整无障碍检测链。小结核心公式contrastRatio (l1 0.05) / (l2 0.05)l1 恒为较亮方0.05 偏移用于模拟真实显示环境、压住极端比值。评级规则普通文字 7.0/4.5 双门槛大字 4.5/3.0 双门槛均取闭区间不满足最低门槛即为Fail。实现要点先统一计算 ratio再按 AAA → AA → Fail 的高优先级顺序分档两套门槛可数据化共享同一判定顺序。系列定位该题challengeType 28即dailyChallengeJs是 freeCodeCamp 每日 JavaScript 挑战第 353 题与 352、354 构成无障碍对比度主题的三步递进完整管线为 RGB → 相对亮度 → 对比率 → 评级。掌握本题后你可以把同一套阈值逻辑直接迁移到前端的无障碍自查脚本中取计算样式里的color与background-color换算出两个相对亮度再套用本文的分档函数即可对页面文本做 AAA/AA/Fail 的快速体检。【免费下载链接】freeCodeCampfreeCodeCamp.orgs open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考