跳转到内容

颜色对比度

1. 理论基石:为什么只关注“亮度”?

Section titled “1. 理论基石:为什么只关注“亮度”?”

在 W3C (万维网联盟) 的无障碍阅读性能评估中,发现 色调(Hue)和饱和度(Saturation)对易读性的影响微乎其微。起决定性作用的是两者的 相对亮度(Luminance) 差异。只要亮度对比足够大,即使是存在严重色觉障碍(色盲)的人群也能清晰阅读。

对比度的取值范围在 1:1(完全相同,如白底白字)与 21:1(极限对比,如黑底白字)之间。

  • A 级:对比度达到 3:1。(针对大号加粗字体可接受的最低极限)。
  • AA 级:对比度达到 4.5:1。(常规文本的法定最低标准,可覆盖轻度视力损失人群)。
  • AAA 级:对比度达到 7:1。(最高黄金标准,为严重视力受损者设计)。

2. 核心算法:纯 JS 实现亮度与对比度计算

Section titled “2. 核心算法:纯 JS 实现亮度与对比度计算”

计算公式严格遵循 W3C 的相对亮度标准方程。

export default {
/**
* 第一步:计算 sRGB 色彩空间的相对亮度 (Luminance)
*/
luminanace(r, g, b) {
var a = [r, g, b].map(function (v) {
v /= 255;
// Gamma 校正与线性化处理
return v <= 0.03928
? v / 12.92
: Math.pow((v + 0.055) / 1.055, 2.4);
});
// 亮度权重:人眼对绿色最敏感,蓝色最迟钝
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
},
/**
* 第二步:对比计算 (返回 1 ~ 21 之间的比值)
*/
contrast(rgb1, rgb2) {
// Hex 转换为 RGB 对象
var result1 = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(rgb1);
var result2 = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(rgb2);
var rgb1Obj = result1 ? { r: parseInt(result1[1], 16), g: parseInt(result1[2], 16), b: parseInt(result1[3], 16)} : null;
var rgb2Obj = result2 ? { r: parseInt(result2[1], 16), g: parseInt(result2[2], 16), b: parseInt(result2[3], 16)} : null;
var lum1 = this.luminanace(rgb1Obj.r, rgb1Obj.g, rgb1Obj.b);
var lum2 = this.luminanace(rgb2Obj.r, rgb2Obj.g, rgb2Obj.b);
// 公式:(最亮色 + 0.05) / (最暗色 + 0.05)
var brightest = Math.max(lum1, lum2);
var darkest = Math.min(lum1, lum2);
return (brightest + 0.05) / (darkest + 0.05);
}
}
// 测试:纯白与纯黄的对比度极低,极难阅读
// console.log(contrast('#ffffff', '#ffff00')); // 输出: 1.0738...

3. 进阶:如何穿透 DOM 树获取元素“真实的背景色”

Section titled “3. 进阶:如何穿透 DOM 树获取元素“真实的背景色””

在复杂的 Web 页面中,元素的 background-color 可能是透明的(如 rgba(0,0,0,0)display:none),此时肉眼看到的颜色其实是透出的 父级或祖父辈节点的背景色。必须递归查询。

(function(WDS, undefined){
// 获取经过浏览器最终渲染计算后的层叠样式
function getStyle(elem, property){
if(!elem || !property) return false;
var value = elem.style[camelize(property)], css;
if(!value && document.defaultView && document.defaultView.getComputedStyle){
css = document.defaultView.getComputedStyle(elem, null);
value = css ? css.getPropertyValue(property) : null;
}
return [value, elem];
}
function camelize(str) {
return str.replace(/-(\w)/g, function (strMatch, p1){ return p1.toUpperCase(); });
}
// 检测截获到的背景色是否具备视觉有效性
function checkBgValue(elem){
var value = getStyle(elem, 'background-color')[0];
var hasColor = value ? true : false;
// 排除透明、不可见、未渲染的干扰节点
if(value == "transparent" || value == "rgba(0, 0, 0, 0)"){
hasColor = false;
} else if(getStyle(elem, 'opacity')[0] == "0" ||
getStyle(elem, 'visibility')[0] == "hidden" ||
getStyle(elem, 'display')[0] == "none"){
hasColor = false;
}
return hasColor;
}
// 递归向上追溯真实的非透明视觉背景色
function getRealBg(elem){
if(checkBgValue(elem)){
return getStyle(elem, 'background-color')[0];
} else if(elem != document.documentElement){
// 如果自身没颜色,找老子要颜色
return getRealBg(elem.parentNode);
}
return ''; // 追溯到顶依然没有
}
WDS.getRealBg = getRealBg;
})(window.WDS || (window.WDS = {}));
// 调用示例
// console.log(WDS.getRealBg(document.getElementById("myText")));