如何在JavaScript中复刻Bootstrap的WCAG算法选择激活态颜色?
实现Bootstrap 5按钮hover/active状态的文本颜色自动选择逻辑
Bootstrap 5的按钮文本颜色自动切换核心是基于WCAG 2.1的对比度标准,通过计算背景色的相对亮度,判断使用黑色(#000000)还是白色(#FFFFFF)文本,来满足最小对比度要求(默认min-contrast-ratio为4.5)。以下是完整的JavaScript实现方案:
核心步骤说明
- 将任意格式的颜色(Hex/RGB/RGBA/颜色关键字)转换为标准RGB数值(0-255)
- 根据WCAG 2.1公式计算背景色的相对亮度
- 计算白色、黑色与背景色的对比度
- 选择满足最小对比度要求的颜色;若两者都满足,选对比度更高的;若都不满足,选与背景相对亮度差值更大的颜色
完整JavaScript代码
// 辅助函数:将Hex颜色转为RGB数组 function hexToRgb(hex) { const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; hex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b); const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? [ parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16) ] : null; } // 辅助函数:将RGB/RGBA字符串转为RGB数组 function rgbToRgbArray(rgbStr) { const result = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*\d+(?:\.\d+)?)*\)$/.exec(rgbStr); return result ? [ parseInt(result[1], 10), parseInt(result[2], 10), parseInt(result[3], 10) ] : null; } // 转换任意颜色格式为RGB数组 function getRgbColor(color) { if (color.startsWith('#')) return hexToRgb(color); if (color.startsWith('rgb') || color.startsWith('rgba')) return rgbToRgbArray(color); // 支持颜色关键字(如'black'、'green') const tempEl = document.createElement('div'); tempEl.style.color = color; document.body.appendChild(tempEl); const computedColor = window.getComputedStyle(tempEl).color; document.body.removeChild(tempEl); return rgbToRgbArray(computedColor); } // WCAG 2.1 相对亮度计算 function getRelativeLuminance(rgb) { const [r, g, b] = rgb.map(val => { const normalized = val / 255; return normalized <= 0.03928 ? normalized / 12.92 : Math.pow((normalized + 0.055) / 1.055, 2.4); }); return 0.2126 * r + 0.7152 * g + 0.0722 * b; } // 计算两种颜色的对比度 function getContrast(l1, l2) { const lighter = Math.max(l1, l2); const darker = Math.min(l1, l2); return (lighter + 0.05) / (darker + 0.05); } // 主函数:根据背景色获取合适的文本颜色 function getButtonTextColor(backgroundColor, minContrastRatio = 4.5) { const bgRgb = getRgbColor(backgroundColor); if (!bgRgb) return '#000000'; // 异常情况默认返回黑色 const bgLuminance = getRelativeLuminance(bgRgb); const whiteLuminance = getRelativeLuminance([255, 255, 255]); const blackLuminance = getRelativeLuminance([0, 0, 0]); const whiteContrast = getContrast(bgLuminance, whiteLuminance); const blackContrast = getContrast(bgLuminance, blackLuminance); // 判断满足最小对比度的颜色 const whiteMeets = whiteContrast >= minContrastRatio; const blackMeets = blackContrast >= minContrastRatio; if (whiteMeets && !blackMeets) return '#FFFFFF'; if (blackMeets && !whiteMeets) return '#000000'; if (whiteMeets && blackMeets) { // 两者都满足,选对比度更高的 return whiteContrast > blackContrast ? '#FFFFFF' : '#000000'; } // 两者都不满足,选与背景亮度差值更大的极端颜色 return Math.abs(bgLuminance - whiteLuminance) > Math.abs(bgLuminance - blackLuminance) ? '#FFFFFF' : '#000000'; }
使用示例
// 测试不同背景色 console.log(getButtonTextColor('#000000')); // 黑色背景 → 返回白色#FFFFFF console.log(getButtonTextColor('#FFFFFF')); // 白色背景 → 返回黑色#000000 console.log(getButtonTextColor('#28a745')); // Bootstrap绿色 → 返回白色#FFFFFF console.log(getButtonTextColor('#ffc107')); // Bootstrap黄色 → 返回黑色#000000
与Bootstrap逻辑的对应
- 当背景为纯黑/纯白时,直接返回对比色,对应你观察到的极端情况
- 对于绿色、蓝色这类中间色,通过
minContrastRatio(默认4.5)判断,和Bootstrap的$min-contrast-ratio变量逻辑完全一致
内容的提问来源于stack exchange,提问作者Danilo Bassi
相关产品推荐
相关产品推荐

