直接用darken()/lighten()会翻车,因hsl调亮与wcag感知亮度非线性;需用luma()估算、mix()加权混合,并以静态色值实测验证。

为什么直接用 darken() 或 lighten() 会翻车
很多人以为给背景色调个 darken(@bg, 60%) 就能当文字色,结果一测只有 3.2:1。问题出在:这些函数基于 HSL 模型调整亮度,而 WCAG 对比度依赖人眼感知的相对亮度(luminance),两者非线性。比如 @bg: #2d2d2d,darken(@bg, 60%) 输出 #0a0a0a,理论对比度 11.2:1,但小字号加抗锯齿后实测掉到 4.3:1 —— 刚好踩线失败。
- 禁用
fade()、spin()、mix():它们不保灰阶差异,易削弱对比 - 透明色(如
rgba(0,0,0,0.8))不能参与对比度计算,工具也读不出最终渲染值 -
currentColor和 CSS 变量(如--text-color)必须先解析成静态 hex/rgb 值再检测
如何用 Less 手动逼近 WCAG 4.5:1
Less 没有内置 contrast() 函数,得靠组合运算+手动 luminance 估算。核心是先实现 luma()(基于 sRGB 转换公式),再反推目标文字色亮度:
- 对浅色背景:从
darken(@bg, 60%)起手,再叠加saturate()补辨识度 - 对深色背景:用
lighten(@bg, 70%)起手,小字号(≤16px)建议直接上lighten(@bg, 75%) - 更稳的做法是用
mix(#ffffff, @bg, 85%)替代darken(),它按亮度加权混合,更贴近 luminance 目标
所有输出必须是静态 #rrggbb 或 rgb(r,g,b),避免变量、透明、currentColor —— 否则 WebAIM Contrast Checker 会误判。
JavaScript 运行时动态校验更可靠
CSS 预处理器只能做近似,真要卡死 4.5:1,得靠运行时校验。用 color 库最省事:
import Color from 'color';
const bg = Color('#f9fafb');
const text = Color('#1f2937');
console.log(text.contrast(bg).toFixed(2)); // → 12.63:1
console.log(text.level(bg)); // → 'AAA'
关键点:
- 必须传入最终渲染色(不是变量、不是 rgba 中间态),从 DevTools 的 Computed 面板复制
rgb()值 - 小字号文本(≤16px)别信理论值,实测验证;Windows ClearType 和 macOS 子像素渲染会让 #333 看起来像 #444
- 按钮禁用态、悬停态、主题切换后的颜色组合,都要单独跑一遍校验
PostCSS 插件能自动化拦截低对比度
开发阶段就防住问题,推荐 postcss-contrast:
- 扫描所有
color+background-color组合,自动算对比度 - 低于 4.5:1 时抛 warning,支持配置自动 fallback(如强制改文字色为 #000)
- 和
postcss-focus配合,同时解决键盘焦点缺失问题
注意:它只分析编译后的 CSS,所以 Less/Sass 里写的变量必须已展开;若用了 filter: contrast() 整体提对比,插件无法识别——那属于视觉增强,不替代文本与背景的原始对比度。
真正难的不是算出一个达标色值,而是覆盖所有字体大小、渲染环境、交互状态下的实测表现。工具只能帮你卡住下限,最终得用人眼+辅助技术+真实用户反馈来闭环。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











