<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">✨ 自动对比度 · 智能文本调整</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
}
body {
min-height: 100vh;
background: #0f0f1c;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem 1rem;
}
.card {
max-width: 1100px;
width: 100%;
background: rgba(255, 255, 255, 0.04);
backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 2rem;
padding: 2rem 1.8rem 2.2rem;
box-shadow: 0 18px 40px rgba(0,0,0,0.6);
}
h1 {
font-size: 1.9rem;
font-weight: 600;
letter-spacing: 1px;
color: #fff;
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 0.5rem;
border-bottom: 1px solid rgba(255,255,255,0.15);
padding-bottom: 0.8rem;
}
h1 span {
background: #7cffb0;
color: #00110a;
font-size: 0.9rem;
padding: 4px 12px;
border-radius: 40px;
font-weight: 600;
letter-spacing: 0.3px;
}
.sub {
color: #b7c0cf;
font-size: 0.95rem;
margin-top: -0.2rem;
margin-bottom: 1.8rem;
}
.grid {
display: grid;
grid-template-columns: 1.6fr 0.9fr 1.6fr;
gap: 1.5rem;
align-items: center;
}
@media (max-width: 760px) {
.grid {
grid-template-columns: 1fr;
gap: 1rem;
}
}
.preview-area {
background: #151522;
border-radius: 1.8rem;
padding: 0.8rem;
border: 1px solid #2d2d3f;
transition: all 0.1s;
}
.preview-box {
background: var(--bg-color, #3498db);
padding: 2.2rem 1.5rem;
border-radius: 1.5rem;
box-shadow: inset 0 2px 8px rgba(0,0,0,0.15);
transition: background-color 0.15s;
min-height: 200px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
border: 2px solid rgba(255,255,255,0.1);
}
.preview-title {
font-size: 2.3rem;
font-weight: 700;
line-height: 1.3;
color: var(--text-color, #fff);
text-shadow: 0 2px 6px rgba(0,0,0,0.2);
transition: color 0.15s;
word-break: break-word;
}
.preview-sub {
font-size: 1.1rem;
font-weight: 400;
margin-top: 0.65rem;
color: var(--text-color, #fff);
opacity: 0.85;
transition: color 0.15s;
}
.chip {
background: rgba(0,0,0,0.3);
backdrop-filter: blur(4px);
padding: 0.25rem 0.8rem;
border-radius: 40px;
font-size: 0.8rem;
border: 1px solid rgba(255,255,255,0.2);
display: inline-flex;
align-items: center;
gap: 6px;
}
.control-panel {
background: #1b1b2a;
padding: 1.5rem 1.3rem;
border-radius: 1.8rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
border: 1px solid #2c2c40;
}
.control-panel h3 {
color: #dde3ef;
font-weight: 500;
font-size: 1.1rem;
margin-bottom: 1.2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.color-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1.2rem;
gap: 12px;
flex-wrap: wrap;
}
.color-row label {
display: flex;
align-items: center;
gap: 8px;
color: #b3c0d6;
font-weight: 500;
font-size: 0.95rem;
}
input[type="color"] {
width: 48px;
height: 48px;
border: 1px solid rgba(255,255,255,0.3);
border-radius: 16px;
background: transparent;
cursor: pointer;
padding: 4px;
}
input[type="color"]::-webkit-color-swatch-wrapper {
padding: 0;
}
input[type="color"]::-webkit-color-swatch {
border: none;
border-radius: 12px;
}
.color-hex {
background: #00000020;
padding: 0.3rem 0.9rem;
border-radius: 40px;
font-family: 'Fira Mono', monospace;
font-size: 0.85rem;
border: 1px solid #3f3f58;
color: #e6eaf5;
}
.divider {
height: 1px;
background: linear-gradient(90deg, transparent, #3d3d5a, transparent);
margin: 1.2rem 0;
}
.auto-badge {
background: #00c2a8;
color: #00110d;
padding: 0.2rem 1rem;
border-radius: 40px;
font-size: 0.8rem;
font-weight: 600;
}
.result-info {
margin-top: 1rem;
background: #0000002c;
border-radius: 1.2rem;
padding: 0.8rem 1rem;
display: flex;
align-items: center;
justify-content: space-between;
border: 1px solid #34344b;
}
.result-item {
display: flex;
flex-direction: column;
align-items: center;
}
.result-item small {
color: #8f9ab0;
font-size: 0.7rem;
text-transform: uppercase;
}
.result-item strong {
color: #fff;
font-size: 1.3rem;
font-weight: 600;
letter-spacing: 0.5px;
}
.result-item .contrast {
background: #2c2c42;
padding: 0.2rem 0.8rem;
border-radius: 80px;
}
.toggle-manual {
background: #22223b;
border: 1px solid #3e3e5b;
border-radius: 60px;
padding: 0.25rem 0.25rem;
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 0.8rem;
}
.toggle-manual button {
flex: 1;
background: transparent;
border: none;
padding: 0.6rem 0.3rem;
border-radius: 40px;
color: #aeb8d0;
font-weight: 500;
cursor: pointer;
transition: 0.15s;
}
.toggle-manual button.active {
background: #00b896;
color: #0a1f1b;
font-weight: 600;
box-shadow: 0 2px 6px rgba(0,200,160,0.2);
}
.manual-colors {
display: flex;
gap: 1rem;
justify-content: center;
margin-top: 1rem;
}
.manual-colors input {
width: 60px;
height: 40px;
border-radius: 12px;
}
.footnote {
margin-top: 2rem;
color: #8593aa;
display: flex;
justify-content: flex-start;
gap: 18px;
flex-wrap: wrap;
font-size: 0.85rem;
border-top: 1px dashed #3a3a52;
padding-top: 1.2rem;
}
.footnote span {
background: #232338;
padding: 0.3rem 1rem;
border-radius: 50px;
display: inline-flex;
align-items: center;
gap: 6px;
}
</style>
</head>
<body>
<div class="card">
<h1>
🌈 自动对比度
<span>LUM</span>
</h1>
<div class="sub">W3C 相对亮度公式 · 自动黑/白文本</div>
<div class="grid">
<!-- 预览区 -->
<div class="preview-area">
<div class="preview-box" id="previewBox">
<div class="preview-title" id="displayText">对比文本</div>
<div class="preview-sub" id="displaySub">背景 · 自动适配</div>
<div style="margin-top: 16px; display: flex; gap: 8px; justify-content: center;">
<span class="chip" id="bgChip"></span>
<span class="chip" id="fgChip"></span>
</div>
</div>
</div>
<!-- 控制区 -->
<div class="control-panel">
<h3>
🎨 背景色
<span class="auto-badge">自动文本</span>
</h3>
<div class="color-row">
<label>
<input type="color" id="bgColorPicker" value="#3498db">
</label>
<span class="color-hex" id="bgHexDisplay">#3498DB</span>
</div>
<div class="divider"></div>
<h3 style="margin-bottom: 10px;">🧠 文本颜色模式</h3>
<div class="toggle-manual">
<button id="autoBtn" class="active" >⚡ 自动 (黑/白)</button>
<button id="manualBtn">🖌️ 手动</button>
</div>
<div id="manualControls" style="display: none;">
<div class="manual-colors">
<div>
<input type="color" id="manualFgColor" value="#ffffff" disabled>
</div>
<div>
<input type="color" id="manualBgOverride" value="#333333" disabled>
</div>
</div>
<div style="display: flex; justify-content: space-between; color: #8492a8; font-size: 0.8rem; padding: 0 0.8rem;">
<span>文字色</span>
<span>备用背景(不生效)</span>
</div>
</div>
<div class="result-info">
<div class="result-item">
<small>背景亮度</small>
<strong id="bgLuminance">0.20</strong>
</div>
<div class="result-item">
<small>文本颜色</small>
<strong id="resolvedTextColor" style="color: #fff;">#FFFFFF</strong>
</div>
<div class="result-item">
<small>对比度</small>
<strong><span id="contrastRatio" class="contrast">9.8</span></strong>
</div>
</div>
</div>
<!-- 附加信息 / 快速预设 -->
<div style="background: #161624; border-radius: 1.8rem; padding: 1.2rem; border: 1px solid #2b2b40;">
<h3 style="color: #d9e3f5; margin-bottom: 1.2rem; font-weight: 500;">🎯 预设背景</h3>
<div style="display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 1rem;">
<button class="preset-btn" style="background:#3498db" data-color="#3498db">蓝</button>
<button class="preset-btn" style="background:#e74c3c" data-color="#e74c3c">红</button>
<button class="preset-btn" style="background:#2ecc71" data-color="#2ecc71">绿</button>
<button class="preset-btn" style="background:#f1c40f" data-color="#f1c40f">黄</button>
<button class="preset-btn" style="background:#9b59b6" data-color="#9b59b6">紫</button>
<button class="preset-btn" style="background:#1abc9c" data-color="#1abc9c">青</button>
<button class="preset-btn" style="background:#000000; border:1px solid #34343f" data-color="#000000">黑</button>
<button class="preset-btn" style="background:#ffffff; color: black; border:1px solid #888;" data-color="#ffffff">白</button>
</div>
<p style="color:#a2adc2; font-size:0.9rem; line-height:1.5;">
⚡ <strong>自动对比规则</strong> · 计算背景相对亮度(Y),使用 WCAG 阈值
<span style="background:#2c2c44; padding:2px 10px; border-radius: 30px;">Y > 0.35 ✔ 文本用黑色</span>
</p>
<div style="background: #1d1d31; border-radius: 16px; padding: 0.6rem 1rem; margin-top: 0.6rem;">
<span style="color: #b7c9ea;">亮度公式: 0.2126R' + 0.7152G' + 0.0722B'</span>
</div>
</div>
</div>
<div class="footnote">
<span>📐 对比度 ≥ 4.5:1 (AA)</span>
<span>🧪 手动模式保留你的选择</span>
<span>💡 实时计算</span>
</div>
</div>
<script>
(function(){
// DOM 元素
const bgColorPicker = document.getElementById('bgColorPicker');
const bgHexDisplay = document.getElementById('bgHexDisplay');
const previewBox = document.getElementById('previewBox');
const displayText = document.getElementById('displayText');
const displaySub = document.getElementById('displaySub');
const bgChip = document.getElementById('bgChip');
const fgChip = document.getElementById('fgChip');
const bgLuminanceEl = document.getElementById('bgLuminance');
const contrastSpan = document.getElementById('contrastRatio');
const resolvedTextColorEl = document.getElementById('resolvedTextColor');
// 手动控件
const autoBtn = document.getElementById('autoBtn');
const manualBtn = document.getElementById('manualBtn');
const manualControls = document.getElementById('manualControls');
const manualFgColor = document.getElementById('manualFgColor');
const manualBgOverride = document.getElementById('manualBgOverride'); // 保留但禁用
let currentMode = 'auto'; // 'auto' 或 'manual'
let manualFg = '#ffffff'; // 手动文本色
// 预设按钮
const presetButtons = document.querySelectorAll('.preset-btn');
// ---------- 工具函数 ----------
// 将 #rgb / #rrggbb 转为 {r,g,b} 0-255
function parseHex(hex) {
if (hex.startsWith('#')) hex = hex.slice(1);
if (hex.length === 3) {
return {
r: parseInt(hex[0] + hex[0], 16),
g: parseInt(hex[1] + hex[1], 16),
b: parseInt(hex[2] + hex[2], 16)
};
}
return {
r: parseInt(hex.substr(0, 2), 16),
g: parseInt(hex.substr(2, 2), 16),
b: parseInt(hex.substr(4, 2), 16)
};
}
// 线性化通道
function linearizeChannel(c) {
let s = c / 255;
if (s <= 0.04045) {
return s / 12.92;
} else {
return Math.pow((s + 0.055) / 1.055, 2.4);
}
}
// 计算相对亮度 (YC)
function relativeLuminance(r, g, b) {
return 0.2126 * linearizeChannel(r) + 0.7152 * linearizeChannel(g) + 0.0722 * linearizeChannel(b);
}
// 计算对比度 (WCAG)
function contrastRatio(l1, l2) {
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
// 转为两位hex大写
function toHex2(n) {
return n.toString(16).padStart(2, '0').toUpperCase();
}
function rgbToHex(r, g, b) {
return `#${toHex2(r)}${toHex2(g)}${toHex2(b)}`;
}
// 根据背景亮度 Y 自动选择黑色或白色 (阈值 0.35)
function autoTextColor(bgLum) {
return bgLum > 0.35 ? '#000000' : '#ffffff';
}
// 更新预览样式
function applyUpdate() {
// 获取背景色 hex
const bgHex = bgColorPicker.value;
const bgRGB = parseHex(bgHex);
const bgLum = relativeLuminance(bgRGB.r, bgRGB.g, bgRGB.b);
// 解析最终文本颜色
let textColorHex;
if (currentMode === 'auto') {
textColorHex = autoTextColor(bgLum);
} else {
// 手动模式: 使用手动文本色,并简单应用
textColorHex = manualFg;
}
// 文本色 rgb
const fgRGB = parseHex(textColorHex);
const fgLum = relativeLuminance(fgRGB.r, fgRGB.g, fgRGB.b);
const cr = contrastRatio(bgLum, fgLum);
// 更新预览变量
previewBox.style.setProperty('--bg-color', bgHex);
previewBox.style.setProperty('--text-color', textColorHex);
// 更新文本内容 (显示对比度彩蛋)
displayText.textContent = '对比文本';
const modeLabel = currentMode === 'auto' ? '⚡ 自动' : '🖌️ 手动';
displaySub.textContent = `${bgHex} · ${textColorHex}`;
// 更新 hex 显示
bgHexDisplay.textContent = bgHex.toUpperCase();
// 更新 chip
bgChip.textContent = `BG ${bgHex.toUpperCase()}`;
fgChip.textContent = `TEXT ${textColorHex.toUpperCase()}`;
// 更新数据展示
bgLuminanceEl.textContent = bgLum.toFixed(3);
contrastSpan.textContent = cr.toFixed(2);
resolvedTextColorEl.textContent = textColorHex.toUpperCase();
resolvedTextColorEl.style.color = textColorHex;
// 手动界面禁用bg override 忽略 (我们只使用手动文本色,背景没有变化)
// 控制台不需要额外操作
}
// 重置手动按钮状态
function setMode(mode) {
currentMode = mode;
if (mode === 'auto') {
autoBtn.classList.add('active');
manualBtn.classList.remove('active');
manualControls.style.display = 'none';
// 禁用手动颜色选择器
manualFgColor.disabled = true;
manualBgOverride.disabled = true;
} else {
manualBtn.classList.add('active');
autoBtn.classList.remove('active');
manualControls.style.display = 'block';
manualFgColor.disabled = false;
manualBgOverride.disabled = true; // 无效果只是摆设
// 如果当前没有手动色值,从当前文本色获取
if (!manualFg) manualFg = '#ffffff';
}
applyUpdate();
}
// 监听背景颜色变化
bgColorPicker.addEventListener('input', function(e) {
applyUpdate();
});
// 模式切换
autoBtn.addEventListener('click', function() {
setMode('auto');
});
manualBtn.addEventListener('click', function() {
setMode('manual');
});
// 手动颜色变化
manualFgColor.addEventListener('input', function(e) {
manualFg = e.target.value;
applyUpdate();
});
// 预设按钮
presetButtons.forEach(btn => {
btn.addEventListener('click', function() {
const color = this.dataset.color;
bgColorPicker.value = color;
applyUpdate();
});
});
// 手动模式下当更改背景时也为手动的颜色显示 (没有影响但保留)
// 额外: 切换预设时同时保留手动模式的颜色
// 初始化
function init() {
// 设定初始背景色
bgColorPicker.value = '#3498db';
manualFg = '#ffffff';
manualBgOverride.value = '#333333'; // 无实际作用,占位
setMode('auto');
applyUpdate();
}
init();
})();
</script>
</body>
</html>
核心机制与操作说明
您可以通过调整背景色或选择预设,直观地看到文本颜色的智能变化,其核心逻辑基于 WCAG 标准。

- 自动对比度算法:脚本使用 WCAG 规定的相对亮度公式(0.2126R‘ + 0.7152G’ + 0.0722B‘)计算背景色亮度,当背景亮度高于阈值(Y > 0.35)时,自动选用黑色文本;否则选用白色文本,以确保足够的对比度(通常达到 AA 级别)。
- 双模式切换:您可以在“自动”和“手动”模式间切换,自动模式完全由算法决定文本颜色;手动模式则允许您通过颜色选择器自由指定文本颜色,但同时会显示该手动颜色与背景的实际对比度,方便您自行判断。
- 实时反馈与预设:所有调整(背景色、模式、手动颜色)都会立即反映在预览卡片上,并同步更新背景亮度、文本颜色和对比度数值,页面右侧提供了多种预设背景色按钮,方便您快速测试不同颜色方案的效果。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播