Zapewnianie czytelności tekstu dzięki dynamicznym kolorom tła

Funkcje internetowe: contrast-color()

Podczas tworzenia komponentów interfejsu wielokrotnego użytku (takich jak plakietki lub przyciski) lub obsługi motywów dynamicznych zapewnienie czytelności tekstu na nieprzewidywalnym kolorze tła może być trudne. Funkcja CSS contrast-color() przyjmuje wartość black lub white, maksymalizując współczynnik kontrastu w stosunku do określonego koloru tła.

Określanie optymalnego koloru tekstu

Gdy element ma zmienny lub dynamicznie wstawiany kolor tła, możesz polegać na przeglądarce, która obliczy najbezpieczniejszy kolor tekstu.

OBOWIĄZKOWE: ustaw właściwość color za pomocą funkcji contrast-color(), przekazując kolor tła (lub jego właściwość niestandardową) jako argument.

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

NIE: używaj contrast-color() do background-color. Została zaprojektowana specjalnie do kolorów pierwszego planu (takich jak tekst lub obramowanie), aby kontrastowały z danym kolorem tła.

Integracja z motywami

contrast-color() akceptuje dowolną prawidłową wartość <color>, w tym właściwości niestandardowe CSS, dlatego jest szczególnie przydatna w systemach projektowania. Dzięki scentralizowaniu motywów tła jako zmiennych kontrast treści na pierwszym planie jest automatycznie aktualizowany przy każdej zmianie motywu, nawet w przypadku kolorów określonych przez użytkownika.

ZALECANE: odwołuj się do udostępnionych zmiennych tła w funkcji contrast-color(), aby zapewnić synchronizację w aplikacji.

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

NIE: nie koduj na stałe osobnego koloru tekstu dla każdego stanu interakcji, gdy tło jest dynamiczne. Spowoduje to ponowne wystąpienie niezgodności, której zapobiega contrast-color().

Strategie zastępcze

Aby obsługiwać przeglądarki bez contrast-color(), musisz podać kreację zastępczą. Jeśli kolor tła jest znany i stały, użyj zakodowanego na stałe koloru kontrastującego. W przypadku dynamicznych teł, których kolor jest nieznany, wybierz strategię z tabeli poniżej na podstawie wymagań interfejsu i docelowych przeglądarek.

Strategia Najlepsze zastosowanie Uwagi
Względna składnia kolorów Automatyczne, wysokiej jakości obliczanie kontrastu tylko za pomocą CSS. Najwyższej jakości kreacja zastępcza tylko w CSS.
Cień tekstu Szybka poprawa czytelności na dowolnym tle. Może wyglądać na „brudny” lub „świecący” i nie pasować do wszystkich projektów.
Obrys tekstu Zachowanie grubości czcionki przy jednoczesnym zapewnieniu kontrastu krawędzi. Aby uniknąć pocieniania kształtów liter, użyj właściwości paint-order, jeśli jest dostępna.
Półprzezroczysta nakładka Zapewnienie minimalnego obszaru kontrastu za tekstem. Zmienia wygląd koloru tła pod tekstem, wymaga osobnego elementu tekstowego.
Filtry SVG Reaktywny kontrast, który aktualizuje się wraz ze zmianą tła. Wymaga osobnego elementu tekstowego; nieoptymalne wdrożenie.

W przypadku przeglądarek, które obsługują tę funkcję, RCS zapewnia najbardziej niezawodne automatyczne zastępowanie kolorów dynamicznych.

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

Alternatywne usługi porównywania cen

ZALECANE: wybierz kreację zastępczą, która najlepiej pasuje do ograniczeń projektowych, jeśli RCS nie jest wystarczający lub obsługiwany.

Opcja 1. Obrys tekstu

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

Opcja 2. Obrys tekstu o szerokości paint-order, aby zachować kształt liter

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

Opcja 3. Półprzezroczysta nakładka tła

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

Opcja 4. Cień tekstu

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

Opcja 5. Filtry SVG

  1. Wczytaj tę definicję filtra SVG. Jeśli chcesz obsługiwać wersje Safari, których dotyczy ten błąd WebKit, który uniemożliwia stosowanie zewnętrznych odwołań i odwołań do adresów URL danych w przypadku filtrów, musisz dodać go bezpośrednio do dokumentu HTML. W przeciwnym razie należy go wczytać z pliku zewnętrznego, czyli pliku SVG lub wbudowanego w 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. Zawijaj tekst w <span>.

    <div class="badge--svg">
      <span>Badge content</span>
    </div>
    
  3. Zastosuj filtr do <span>.

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