资讯详情

别再肉眼猜对比度了——我用华为云码道 Agent 写了个 WCAG 检查器,结果被它自己的徽章上了一课

📅 2026/10/9 2:50:03 | 华诺云谱 👁 阅读
别再肉眼猜对比度了——我用华为云码道 Agent 写了个 WCAG 检查器,结果被它自己的徽章上了一课
别再肉眼猜对比度了——我用码道 Agent 写了个 WCAG 检查器结果被它自己的徽章上了一课一键开通华为云码道 CodeArts 代码智能体https://developer.huaweicloud.com/codeartsco.html?sourcedmzntgwatomgit1sourceaddmzntgwatomgithd一、这玩意儿是干嘛的前端写页面、UI 出设计稿、做适老化改造都绕不开一个问题这个字色放这个底色上看得清吗够不够无障碍标准肉眼判断极不靠谱——你觉得挺清楚的截图一量可能连及格线都没到。我做了个网页选前景色、背景色它实时算出对比度比值比如15.76:1告诉你过没过 WCAG 的 AA/AAA不合格还能一键帮你把颜色改到合格。白底、干净、专业——这次特意没用之前那套深色。代码是喂华为云码道 CodeArts Agent一轮轮建的仓库atomgit.com/wangleizi/contrast-auditor22 项测试全绿。但这篇真正想聊的是它被自己的评审揪出来的两个打脸瞬间——一个关于验证一个关于它自己。二、为什么做这个我前阵子帮朋友的一个政务小程序做适老化自查产品经理信誓旦旦按钮文字看得清。我拿 WebAIM 一测浅灰配白底对比度 1.9:1标准要求 4.5:1差着一倍多。老人根本看不清。那一刻我就想这种肉眼觉得行、一量就不行的坑太常见了而它偏偏有精确的数学答案——WCAG 的对比度公式是写死的规范输入两个颜色输出一个确定的比值没有模糊空间。有客观真值的东西就适合做可验证的小工具。而且这个赛道有意思的地方在于市面上不是没有对比度检查器WebAIM、axe、Chrome DevTools 都有。但它们要么是网页版要么要装插件我要的是一个零依赖、能 clone 下来自己跑、还能顺手把不合格颜色改好的小工具——重点是改不只是告诉你不行。这个自动修色的功能才是它和现成轮子的区别。选题三条铁律一秒看懂、有客观真值、别撞车。配色检查器切的是前端工程 无障碍合规这条纯工具赛道跟之前那些炫技 demo升旗、月相、3D 日地月和会算题的烟花、会听音准的调音器完全不是一路而且踩中适老化政策热点。三、先跑起来看看零依赖纯 Node 20 ES Modulegitclone https://atomgit.com/wangleizi/contrast-auditor.gitcdcontrast-auditornode--test# 22 项测试全绿nodebin/dev.mjs# http://localhost:5173左侧选色、右侧实时出比值和徽章底部一排常见 UI 配色一键填入。挑一个不合格的浅灰试试浅灰#cccccc配白底比值只有 1.61三个徽章全红工具直接告诉你把前景色改成 #767676 就到 4.54 了点一下就能应用。这个不只是告诉你不行、还告诉你怎么改的设计是我觉得它比现成检查器多走的一步。四、怎么钉码道 Agent老规矩提示词钉到函数级结尾挂铁律直接建文件并git add -A git commit git push不要只描述、不要贴代码正文只回复 git log 文件树 测试 pass/fail。第一轮提示词原样【R1 · 无障碍配色对比度检查器 contrast-auditor 核心算法层】 建 public 仓库 contrast-auditor。Node 20 ES Module零依赖node:test。 - src/wcag.mjs: relativeLuminancesRGB 线性化 c0.03928?c/12.92:((c0.055)/1.055)^2.4 L0.2126R0.7152G0.0722B、contrastRatio(L亮0.05)/(L暗0.05)、wcagLevel - 硬断言: L(#fff)≈1.0、ratio(#000,#fff)≈21.0、ratio(#767676,#fff)≈4.54、#777 判 AAfalse 直接建文件并 git commit git push只回复 git log 文件树 测试 pass/fail切 5 轮喂R1 核心公式、R2 参考真值锚定、R3 白底前端、R4 自动修色对拍报告、R5 README。每轮本地 clone 复核。这里补充一下分轮到底为什么有用。我一开始想一把梭一条提示词让它把整个项目建完结果它建到一半上下文吃紧就开始偷工减料、甚至把前面的接口忘了。分成五轮、每轮一个能独立交付的东西之后每一轮它都在一个相对干净的上下文里干活质量明显稳。而且每轮跑完我本地git clone下来node --test复核——码道说绿不算绿我本地跑绿才算绿。这条我从第一个项目踩坑踩出来一直沿用。还有个技巧结尾那句只回复 git log 文件树 测试 pass/fail非常关键。不加这句它特别爱在对话里贴一大堆代码正文给你欣赏既费 token 又容易和真实提交的文件对不上。加了这句它就老老实实只报结果。五、架构一条公式链 一层外部锚定color(解析 #hex/rgb) → wcag(相对亮度对比度) → analyzer(AA/AAA判定) → autofix(改到合格) ↑ verify(15 组官方参考对 独立库差分对拍)方案做法我的取舍直接调 WebAPI 现成库最省事❌ 那还做什么项目且黑盒自己实现 WCAG 公式可控、可验证✅ 就它再用外部锚定自证清白六、核心算法掰开揉碎先说颜色解析得同时吃#fff、#ffffff、rgb(37,99,235)三种写法// src/color.mjsexportfunctionparseColor(input){constsinput.trim();letm;if((ms.match(/^#([0-9a-f]{3})$/i)))return{r:parseInt(m[1][0].repeat(2),16),g:parseInt(m[1][1].repeat(2),16),b:parseInt(m[1][2].repeat(2),16)};if((ms.match(/^#([0-9a-f]{6})$/i)))return{r:parseInt(m[1].slice(0,2),16),g:parseInt(m[1].slice(2,4),16),b:parseInt(m[1].slice(4,6),16)};if((ms.match(/^rgb\(\s*(\d)[,\s](\d)[,\s](\d)\s*\)$/i)))return{r:m[1],g:m[2],b:m[3]};thrownewError(无法解析颜色:${input});}WCAG 的对比度看着玄其实是两步确定性的算术。第一步算相对亮度先把 sRGB 通道线性化这里有个坑待会儿说再加权和// src/wcag.mjsfunctionlin(c8){// c8 是 0-255constcc8/255;returnc0.03928?c/12.92:Math.pow((c0.055)/1.055,2.4);}exportfunctionrelativeLuminance({r,g,b}){return0.2126*lin(r)0.7152*lin(g)0.0722*lin(b);}exportfunctioncontrastRatio(fg,bg){constL1relativeLuminance(fg),L2relativeLuminance(bg);const[hi,lo]L1L2?[L1,L2]:[L2,L1];return(hi0.05)/(lo0.05);}第二步判等级普通文字和大字号阈值不同// src/wcag.mjsexportfunctionwcagLevel(ratio,isLargeTextfalse){returnisLargeText?{AA:ratio3.0,AAA:ratio4.5}:{AA:ratio4.5,AAA:ratio7.0};}自动修色是保持色相不变、沿向黑或向白方向线性缩放亮度、二分搜索直到对比度达标——不是全局最优是离原色最近的合格色这个取舍我写进了 README// src/autofix.mjs —— 保持色相向黑/白缩放找最近合格色exportfunctionnearestAccessible(fg,bg,target4.5){consttowardBlackcontrastRatio(fg,bg)contrastRatio(scale(fg,0),bg);constdirtowardBlack?0:1;// 0向黑, 1向白letlodir0?0:1,hidir0?1:0;for(leti0;i60;i){// 60 次二分足够到 1e-18constmid(lohi)/2,cscale(fg,mid);if(contrastRatio(c,bg)target)himid;elselomid;}returnscale(fg,hi);}七、第一次打脸我的权威参考值有一半是自己算的R2 我让码道建了 15 组官方参考对比值跑对拍 max_abs_diff0.0047我挺得意。结果技术评审 Agent 一句戳穿15 条里约 8 条是真外部锚点21.0、4.54、4.48、2.85、12.63 这些公认值但另外 5 条标着WCAG 公式核算的是用被测的同一套公式算出来再四舍五入硬写死的——公式写错也测不出属自证。标着破自证的测试名不副实。我一看还真是。#1a2333、#16a34a、#000/#eee那几条的 officialRatio是我自己算一遍填进去的然后断言我算的等于我算的。跟上一篇潮汐、上上一篇冲奶钟的坑一模一样自己当运动员又当裁判。怎么破我加了个独立实现差分测试——引入第三方库wcag-contrast另一拨人写的、另一套代码让它的hex(fg,bg)和我的contrastRatio对 20 组色对逐值对拍// test/differential.test.mjs —— 拿独立实现当外部真值importwc_pkgfromwcag-contrast;for(const[fg,bg]ofPAIRS){// 20 组含 #ff0000/#00ff00、#123456/#abcdefconstourscontrastRatio(parseColor(fg),parseColor(bg));constrefwc_pkg.hex(fg,bg);// 第三方独立库assert.ok(Math.abs(ours-ref)0.01);}// differential vs wcag-contrast: 20 pairs, max_abs_diff0.00000两个完全独立的代码库、同一份规范逐值对到小数点后五位一致——这才叫破自证。同时把那 5 条自算参考值的 source 老老实实改成self-computed(仅参考)不再冒充外部权威。顺带说下这个差分测试为什么比15 组参考值更硬参考值对拍本质还是我的输出 我提前写死的期望只要期望是我自己算的就跳不出自证。而独立库对拍是我的输出 别人独立实现的输出两边代码不同、作者不同、bug 也不会一样能对上才说明大家都真的对齐了规范。这也是我做完三个项目才彻底想明白的一件事验证的强度取决于你的参照物离你有多远。analyzer 把这几层串起来对外只吐一个能不能用、怎么改的结论// src/analyzer.mjsexportfunctionanalyze(fgInput,bgInput,{largefalse}{}){constfgparseColor(fgInput),bgparseColor(bgInput);constratiocontrastRatio(fg,bg).toFixed(2);const{AA,AAA}wcagLevel(ratio,large);return{ratio,AA,AAA,suggestion:AA?已满足 AA无需调整:把前景色改为${hex(nearestAccessible(fg,bg,large?3.0:4.5))}可达标,};}八、第二次打脸这个检查对比度的工具自己的徽章没过对比度UI 评审 Agent 的第三条意见我看了直乐又脸红.badge.pass用 #16a34a 文字配 #f0fdf4 浅绿底对比度只有约 3.2:1没过它自己判定的 AA 标准。一个专门查颜色看不看得清的工具它自己的通过徽章恰恰看不清。这不就是灯下黑吗。我拿它自己算了一下确实 3.2:1。改成深一档的 #15803d 后是 4.79:1稳过顺手发现未通过红徽章 #dc2626 配浅红底也只有 4.41一并改成 #b91c1c5.91。这个 bug 特别有象征意义做工具的容易只盯着功能对不对忘了工具自己也得吃自己的狗粮。现在我给这个项目加的测试里有一条专门断言UI 上所有前景/背景组合都得过 AA——让它天天自查。九、测试22 项// 外部锚定assert.ok(Math.abs(contrastRatio(parseColor(#000),parseColor(#fff))-21)0.01);assert.ok(Math.abs(contrastRatio(parseColor(#767676),parseColor(#fff))-4.54)0.02);// 独立实现差分真·破自证// 边界#777 判 AAfalse#767676 判 AAtrue// autofix不合格色改后必过 AA 且离原色最近这 22 项测试里我特意让断言锚定的是外部公认值而不是我算出来的值。比如#767676/#ffffff 4.54是 W3C 文档里举例说明AA 边界灰的那个经典值#000/#fff 21是理论上限#777/#fff 4.48刚好卡在 4.5 下面一点——我拿这几个卡边界就是为了验证公式写错一点点就会被测出来。再加上第七节那个独立库差分测试等于给核心公式上了双保险一条对权威值一条对另一个实现。十、真实的坑坑 10.03928 还是 0.04045。sRGB 线性化的拐点常量有两个版本WCAG 2.1 规范原文用的是0.03928而色彩学里更精确的是 0.04045。我用 0.03928 才能跟 WebAIM 对上——这俩混了会导致边界值判定翻转。评审专门夸了这条没踩错。坑 2自证参考值。第七节整节。坑 3工具自己不过自家标准。第八节。坑 4serve.mjs 在 Windows 上 404。码道写的静态服务用new URL(., import.meta.url).pathname拼根路径在 Windows 下 pathname 带个前导斜杠path.resolve出来变成D:\D:\...双盘符页面全 404。改成fileURLToPath才好。跨平台路径这块 AI 是真的容易翻车。坑 5码道 push 认证中途又崩了。R6 补差分测试时它的 ag 把 git remote 的 token 改没了本地提交推不上去。这个我最后单独处理。其实还有第零个坑就是白底这件事本身。之前三个项目我全用的深底冷蓝看腻了这次特意让码道从 R3 起就按白底规范来纯白背景、深灰正文、单一蓝强调、状态色只在徽章上点一下。结果白底比黑底更难藏拙——黑底上随便加点发光渐变就显得高级白底全靠留白、层级、字号撑反而更考验设计。UI 评审给白底挑的刺也明显比给黑底的多。十一、提效数据环节码道 Agent我建仓库 写全部文件✅出题WCAG 公式实现✅一次对验收识破自证参考值❌✅ 评审独立库差分测试✅ 实现定方案发现徽章不过 AA❌✅ 评审Windows 路径 bug❌写坏的✅ 修一句话公式实现码道一次做对但验证到底成不成立工具自己合不合规这种反思它给不了反而会把自证当证据交给你。我算了下时间账从建仓库到 22 项测试全绿、白底 UI 能跑码道前后花了不到四十分钟而真正让这个项目立得住的三个动作——把自算参考值换成独立库对拍、揪出徽章自己不过 AA、修 Windows 路径 bug——没一个是它主动想到的全是评审加我盯出来的。所以这系列做到第四个我越来越确信一件事AI 时代做出来越来越便宜做对越来越贵而对这个字暂时还得人来定义。十二、五维自检 写在最后眼前一亮别再肉眼猜对比度一键量 一键修。可验证护城河15 组官方值对拍 max_abs_diff 0.0047 独立库 wcag-contrast 差分 20 组 max_abs_diff 0.00000node --test一键复现。原创度把自证和灯下黑两个真实教训做成叙事核心。工程质量22 测试、零依赖、白底规范。诚实度0.03928 取舍、autofix 非全局最优、APCA/色觉未覆盖全写明。顺带交代两个还没做的一是 APCAWCAG 3 草案里那套更贴合人眼感知的对比度算法它和 2.1 的公式结果会有出入我暂时只做了 2.1二是色觉障碍模拟红绿色盲看这对颜色是什么效果这个对设计走查很有用留作下一篇的扩展。工具不大但我知道它的边界在哪这比堆功能重要。最后说点感受。这个项目最值钱的两行代码不是 WCAG 公式那玩意儿规范里抄的是那两条评审揪出来的一条逼我把自己算的参考值换成独立库对拍一条逼我这个查对比度的工具先把自己的徽章查了一遍。做工具最怕的不是功能少是自洽的错觉——自己算、自己测、自己信闭环了但和现实没接上。写到这我连着做了四个会算 XX 的小工具发现一个规律AI 把写出来的成本几乎打到了零但写对了和写出来是两回事中间隔着的那道鸿沟恰恰是人的价值所在。潮汐那次我把同窗自洽当独立验证奶瓶那次我把同源算法当三路互证这次我先把自算的参考值当权威、还让一个查对比度的工具自己的对比度不达标——四次踩的是同一个坑的四个变体把自己跟自己一致误当成我对。我现在给自己立了条铁律任何看起来在自我验证的东西先逼自己回答一句——“如果我把这个结论扔掉还有什么离我更远的证据能撑起它” 独立库对拍、真实观测数据、第三方权威值都是离我更远的证据。答不上来就是自证。这条放在写代码上叫交叉验证放在别的地方大概也叫独立思考。欢迎 clone 下来把你页面里那些看着还行的配色丢进去量一量大概率会和我一样被打脸https://atomgit.com/wangleizi/contrast-auditor
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑