Đảm bảo văn bản dễ đọc bằng màu nền động

Tính năng web: contrast-color()

Khi xây dựng các thành phần giao diện người dùng có thể sử dụng lại (như huy hiệu hoặc nút) hoặc hỗ trợ giao diện động, việc đảm bảo văn bản dễ đọc trên màu nền không thể đoán trước có thể là một thách thức. Hàm CSS contrast-color() phân giải thành black hoặc white, tối đa hoá tỷ lệ tương phản so với màu nền đã chỉ định.

Xác định màu văn bản tối ưu

Khi một phần tử có màu nền biến đổi hoặc được chèn linh động, bạn có thể dựa vào trình duyệt để tính toán màu văn bản an toàn nhất.

BẮT BUỘC: Đặt thuộc tính color bằng hàm contrast-color(), truyền màu nền (hoặc thuộc tính tuỳ chỉnh của màu nền) làm đối số.

.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));
}

KHÔNG NÊN: Sử dụng contrast-color() cho background-color. Hàm này được thiết kế riêng cho màu nền trước (như văn bản hoặc đường viền) để tương phản với một màu nền nhất định.

Tích hợp với giao diện

Vì contrast-color() chấp nhận mọi giá trị <color> hợp lệ, bao gồm cả các thuộc tính tuỳ chỉnh CSS, nên thuộc tính này đặc biệt hiệu quả trong các hệ thống thiết kế. Bằng cách tập trung các giao diện nền dưới dạng biến, bạn đảm bảo rằng nội dung trên nền trước sẽ tự động cập nhật độ tương phản bất cứ khi nào giao diện thay đổi, ngay cả với màu do người dùng chỉ định.

NÊN: Tham chiếu các biến nền dùng chung bên trong contrast-color() để đảm bảo đồng bộ hoá trên toàn ứng dụng.

.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);
}

KHÔNG NÊN: Cố định giá trị trong mã cho màu văn bản riêng biệt cho từng trạng thái tương tác khi nền là động. Việc này sẽ gây ra tình trạng không khớp mà contrast-color() được thiết kế để ngăn chặn.

Chiến lược dự phòng

Để hỗ trợ các trình duyệt không có contrast-color(), bạn phải cung cấp một phương án dự phòng. Nếu màu nền đã biết và cố định, hãy sử dụng màu tương phản được cố định giá trị trong mã. Đối với nền động không xác định được màu, hãy chọn một chiến lược trong bảng dưới đây dựa trên yêu cầu về giao diện người dùng và mục tiêu hỗ trợ trình duyệt.

Chiến lược Phù hợp nhất cho: Lưu ý
Cú pháp màu tương đối Tính toán độ tương phản tự động, chất lượng cao, chỉ dùng CSS. Dự phòng chỉ CSS chất lượng cao nhất.
Đổ bóng văn bản Nhanh chóng tăng khả năng đọc trên mọi nền. Có thể trông "bẩn" hoặc "phát sáng"; có thể không phù hợp với tất cả thiết kế.
Nét chữ Giữ nguyên độ đậm của phông chữ trong khi vẫn đảm bảo độ tương phản của cạnh. Sử dụng paint-order để tránh làm mỏng hình dạng chữ cái (nếu có).
Lớp phủ mờ Đảm bảo vùng tương phản tối thiểu phía sau văn bản. Thay đổi giao diện của màu nền dưới văn bản, yêu cầu một phần tử văn bản riêng biệt.
Bộ lọc SVG Độ tương phản thích ứng, cập nhật khi nền thay đổi. Yêu cầu một phần tử văn bản riêng biệt; triển khai theo cách tạm thời.

Đối với những trình duyệt hỗ trợ, RCS cung cấp tính năng dự phòng tự động mạnh mẽ nhất cho màu động.

@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 dự phòng thay thế

NÊN: Chọn phương án dự phòng phù hợp nhất với các quy tắc ràng buộc về thiết kế nếu RCS không đủ hoặc không được hỗ trợ.

Cách 1: Đường viền văn bản

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

Cách 2: Đường viền văn bản bằng paint-order để giữ nguyên hình dạng chữ cái

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

Cách 3: Lớp phủ nền mờ

.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;
}

Lựa chọn 4: Đổ bóng văn bản

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

Phương án 5: Bộ lọc SVG

  1. Tải định nghĩa bộ lọc SVG này. Bạn phải thêm trực tiếp vào tài liệu HTML nếu cần hỗ trợ các phiên bản Safari chịu ảnh hưởng của lỗi WebKit này, lỗi này ngăn chặn các tham chiếu URL dữ liệu và bên ngoài cho bộ lọc. Nếu không, bạn nên tải biểu tượng từ một tệp bên ngoài, có thể là tệp SVG hoặc được chèn cùng dòng trong 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. Gói văn bản trong <span>.

    <div class="badge--svg">
      <span>Badge content</span>
    </div>
    
  3. Áp dụng bộ lọc cho <span>.

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