自动调整文本对比度的脚本

wen 实用脚本 2
<!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 级别)。
  • 双模式切换:您可以在“自动”和“手动”模式间切换,自动模式完全由算法决定文本颜色;手动模式则允许您通过颜色选择器自由指定文本颜色,但同时会显示该手动颜色与背景的实际对比度,方便您自行判断。
  • 实时反馈与预设:所有调整(背景色、模式、手动颜色)都会立即反映在预览卡片上,并同步更新背景亮度、文本颜色和对比度数值,页面右侧提供了多种预设背景色按钮,方便您快速测试不同颜色方案的效果。

抱歉,评论功能暂时关闭!