使用动态背景颜色确保文本可读性

Web 功能: contrast-color()

在构建可重复使用的界面组件(例如标记或按钮)或支持动态主题时,确保文本在不可预测的背景颜色下清晰易读可能是一项挑战。contrast-color() CSS 函数会解析为 black 或 white,从而最大限度地提高与指定背景颜色的对比度。

确定最佳文字颜色

如果元素的背景颜色是可变的或动态注入的,您可以依靠浏览器来计算最安全的文本颜色。

强制性:使用 contrast-color() 函数设置 color 属性,并将背景颜色(或其自定义属性)作为实参传递。

.badge {
  /* Define the background color as a custom property */
  --badge-bg: #e02424;
  background-color: var(--badge-bg);
  /* MANDATORY: Dynamic contrasting color */
  color: contrast-color(var(--badge-bg));
}

不建议:将 contrast-color() 用于 background-color。它专为前景色(如文本或边框)设计,可与给定的背景色形成对比。

与主题设置集成

由于 contrast-color() 接受任何有效的 <color> 值(包括 CSS 自定义属性),因此在设计体系中尤为强大。通过将背景主题集中为变量,您可以确保每当主题发生变化时,前景内容都会自动更新其对比度,即使主题变为用户指定的颜色也是如此。

建议:在 contrast-color() 中引用共享的后台变量,以确保在整个应用中实现同步。

.theme-card {
  background-color: var(--theme-surface-color);
  color: contrast-color(var(--theme-surface-color));
}

.theme-container[data-theme="light"] {
  --theme-surface-color: #f4f4f4;
}
.theme-container[data-theme="dark"] {
  --theme-surface-color: #1a1a1a;
}
.theme-container[data-theme="custom"] {
  --theme-surface-color: var(--user-custom-theme);
}

请勿:在背景为动态时,为每个互动状态硬编码单独的文本颜色。这样做会重新引入 contrast-color() 旨在防止的不匹配问题。

后备策略

为了支持没有 contrast-color() 的浏览器,您必须提供后备。如果背景颜色已知且固定,请使用硬编码的对比色。对于颜色未知的动态背景,请根据您的界面要求和浏览器支持目标,从下表中选择一种策略。

策略 主打内容 注意事项
相对颜色语法 自动、高质量、仅限 CSS 的对比度计算。 最高质量的纯 CSS 后备。
文字阴影 在任何背景下都能快速提升可读性。 可能看起来“脏”或“发光”;可能不适合所有设计。
文本描边 保留字体粗细,同时确保边缘对比度。 使用 paint-order 可避免字母形状变细(如果可用)。
半透明叠加层 确保文字背后有最小对比度区域。 更改文本下方背景颜色的外观,需要单独的文本元素。
SVG 滤镜 响应式对比度,可随背景变化而更新。 需要单独的文本元素;实现方式不够优雅。

对于支持 RCS 的浏览器,RCS 可为动态颜色提供最强大的自动回退功能。

@supports (color: oklch(from red l c h)) {
  .badge {
    /* Highest threshold that passes WCAG. Higher values may be more legible. */
    --threshold: 0.623;
    --l: max(0, sign(var(--threshold) - l));
    color: oklch(from var(--badge-bg) var(--l) 0 h);
  }
}

@supports (color: contrast-color(red)) {
  .badge {
    color: contrast-color(var(--badge-bg));
  }
}

备用 CSS 后备

建议:如果 RCS 不够用或不受支持,请选择最符合设计限制的后备方案。

选项 1:文字描边

.badge--stroke {
  -webkit-text-stroke: 4px black;
}

方法 2:使用 paint-order 描边文本,以保留字母形状

.badge--stroke {
  -webkit-text-stroke: 4px black;
  paint-order: stroke fill;
}

选项 3:半透明背景叠加层

.badge--overlay {
  position: relative;
  color: #fff;
  overflow: hidden;
}
.badge--overlay::before {
  content: "";
  position: absolute;
  inset: 0;
  background-color: rgb(0 0 0 / 0.4);
  z-index: 0;
}
.badge--overlay span {
  position: relative;
  z-index: 1;
}

选项 4:文字阴影

.badge--shadow {
  text-shadow: 0 1px 3px rgb(0 0 0 / 0.8);
}

选项 5:SVG 滤镜

  1. 加载此 SVG 滤镜定义。如果您需要支持受此 WebKit bug 影响的 Safari 版本(该 bug 会阻止过滤器的外部和数据网址引用),则必须将其直接添加到 HTML 文档中。否则,应从外部文件(SVG 文件或内嵌在 CSS 中的文件)加载。

    <!-- Technique from https://miunau.com/posts/dynamic-text-contrast-in-css/ -->
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="0" style="display: none;">
      <defs>
        <filter id="contrast-filter" color-interpolation-filters="sRGB">
          <feColorMatrix type="matrix"
            values="0.2126 0.7152 0.0722 0 0
              0.2126 0.7152 0.0722 0 0
              0.2126 0.7152 0.0722 0 0
              0 0 0 1 0"/>
          <feMorphology operator="dilate" radius="2"/>
          <feComponentTransfer>
            <feFuncR type="linear" slope="-255" intercept="128"/>
            <feFuncG type="linear" slope="-255" intercept="128"/>
            <feFuncB type="linear" slope="-255" intercept="128"/>
          </feComponentTransfer>
          <feComposite operator="in" in2="SourceGraphic"/>
        </filter>
      </defs>
    </svg>
    
  2. 将文本封装在 <span> 中。

    <div class="badge--svg">
      <span>Badge content</span>
    </div>
    
  3. 将过滤条件应用于 <span>。

.badge--svg span{
  color: var(--badge-bg);
  filter: url(#contrast-filter);
}