CSS color-mix() i kolory względne: rozjaśnienia, hover i przezroczystość
color-mix() miesza dwa kolory w wybranej przez ciebie przestrzeni barw, a składnia kolorów względnych pozwala zmienić jeden kanał istniejącego koloru, na przykład oklch(from var(--brand) calc(l - 0.1) c h). Obie działają w przeglądarce, współpracują ze zmiennymi CSS i zastępują większość funkcji kolorów z Sass. Do rozjaśnień i hovera mieszaj w oklab, do przezroczystości w srgb.
6 min czytania
Jak działa color-mix()
color-mix() przyjmuje przestrzeń barw i dwa kolory z opcjonalnymi procentami, a zwraca kolor pomiędzy nimi. color-mix(in oklab, var(--brand) 90%, black) oznacza 90% koloru marki i 10% czerni, zmieszane w Oklab. Gdy podasz jeden procent, drugi kolor dostaje resztę. Gdy nie podasz żadnego, każdy dostaje 50%.
Przeglądarka liczy wynik w trakcie działania strony, więc działa to z własnymi właściwościami (custom properties), currentColor i przełączaniem motywów. Funkcje Sass wykonują się raz, podczas budowania, i nie widzą wartości var().
Jeśli oba procenty sumują się do mniej niż 100%, wynik staje się też częściowo przezroczysty: color-mix(in srgb, red 30%, blue 30%) to równa mieszanka przy 60% krycia. Trzymaj sumę na 100%, chyba że właśnie tego chcesz.
CSS
.button {
--brand: #2563eb;
background: var(--brand);
}
.button:hover {
background: color-mix(in oklab, var(--brand) 90%, black);
}
.badge {
background: color-mix(in oklab, var(--brand) 10%, white);
border-color: color-mix(in srgb, var(--brand) 30%, transparent);
}W jakiej przestrzeni barw mieszać: srgb, oklab czy oklch
Przestrzeń zmienia wynik bardziej, niż większość osób się spodziewa. Poniżej czysty niebieski #0000ff zmieszany pół na pół z czystym żółtym #ffff00, policzony tą samą matematyką, której używa przeglądarka.
W srgb kanały uśredniają się dokładnie do 128, 128, 128: mieszanka to #808080, szary o zerowej chromie. Oklab też miesza po linii prostej, a niebieski i żółty leżą po jej przeciwnych stronach, więc w dużej mierze się znoszą i dają matowy, szarawy niebieski. Oklch interpoluje zamiast tego kąt barwy, idąc krótszą drogą od 264° do 110°, więc środek to intensywny morski turkus o barwie 187°, który zachowuje chromę obu kolorów wejściowych.
- Niebieski #0000ff#0000ff
- Żółty #ffff00#ffff00
- w srgb: #808080#808080
- w oklab: #6cabc7#6cabc7
- w oklch: #00bdb0#00bdb0
| Przestrzeń | Niebieski + żółty | L, C w OKLCH | Blue-600 + yellow-400 | L, C w OKLCH |
|---|---|---|---|---|
| srgb | #808080 | 0.600, 0 | #909880 | 0.665, 0.035 |
| oklab | #6cabc7 | 0.710, 0.077 | #90a3ad | 0.703, 0.026 |
| oklch | #00bdb0 | 0.710, 0.262 | #00bd9e | 0.703, 0.194 |
Druga para to #2563eb i #facc15 z Tailwind. Wyniki oklch wypadają poza sRGB, więc chroma jest podana przed mapowaniem gamutu, a hex to najbliższy kolor, jaki potrafi wyświetlić ekran sRGB.
Rzucają się w oczy dwie rzeczy. Srgb ląduje na jasności 0,600, podczas gdy percepcyjny środek między niebieskim (0,452) a żółtym (0,968) to 0,710: stąd ciemny, zabłocony środek gradientów w srgb. I tylko oklch zachowuje intensywność mieszanki, za cenę przejścia przez barwę, której nie ma żaden z kolorów wejściowych.
Praktyczna zasada: w oklab mieszaj rozjaśnienia, przyciemnienia i stany hover, gdzie zostajesz przy jednej barwie i chcesz równych kroków jasności. W oklch mieszaj, gdy oba kolory są nasycone i chcesz nasyconego wyniku. W srgb mieszaj przezroczystość, bo tak przeglądarka składa warstwy.
Przepisy: hover, rozjaśnienia i przezroczyste warianty
Wszystkie wartości poniżej używają #2563eb jako --brand. Biały tekst na nim ma współczynnik kontrastu 5,17:1.
Domieszka 10% czerni przyciemnia w oklab mocniej niż w srgb: kontrast białego tekstu rośnie do 6,50:1 wobec 6,11:1. Oklab i oklch dają ten sam wynik, gdy drugim kolorem jest czerń lub biel, bo nie mają one barwy do interpolacji.
| Przepis | CSS | Wynik | Kontrast |
|---|---|---|---|
| Hover, o 10% ciemniej | color-mix(in oklab, var(--brand) 90%, black) | #1f55cc | 6.50:1 z białym tekstem |
| Active, o 20% ciemniej | color-mix(in oklab, var(--brand) 80%, black) | #1947ae | 8.22:1 z białym tekstem |
| Hover mieszany w srgb | color-mix(in srgb, var(--brand) 90%, black) | #2159d3 | 6.11:1 z białym tekstem |
| Rozjaśnione tło, 10% | color-mix(in oklab, var(--brand) 10%, white) | #e8f0ff | 4.51:1 z tekstem w kolorze marki |
| Rozjaśnione tło, 20% | color-mix(in oklab, var(--brand) 20%, white) | #d2e2ff | 3.95:1 z tekstem w kolorze marki |
| Przezroczysty, 20% | color-mix(in srgb, var(--brand) 20%, transparent) | rgba(37, 99, 235, 0.2) | Na bieli wygląda jak #d3e0fb |
Uważaj na rozjaśnienia. Tekst w kolorze marki ma 5,17:1 na bieli, 4,51:1 na rozjaśnieniu 10% i tylko 3,95:1 na rozjaśnieniu 20%, co nie spełnia WCAG AA dla zwykłego tekstu. Jeśli kładziesz tekst w kolorze marki na jej rozjaśnieniu, użyj dla tekstu koloru z hovera.
Mieszanie z transparent zachowuje kolor i obniża tylko kanał alfa, bo przeglądarka miesza wartości z premnożoną alfą. Wynik dopasowuje się do tego, co jest pod spodem: ta sama reguła wygląda jak #d3e0fb na bieli i jak #132651 na ciemnej powierzchni #0f172a. Rozjaśnienie zmieszane z bielą zostaje blade na ciemnej stronie, więc dla komponentów widocznych w obu motywach wersja przezroczysta jest zwykle lepszym wyborem.
Składnia kolorów względnych: zmiana jednego kanału
Składnia kolorów względnych bierze istniejący kolor, rozkłada go na kanały i pozwala przepisać dowolny z nich przez calc(). W oklch(from var(--brand) l c h) słowa kluczowe l, c i h przechowują jasność (od 0 do 1), chromę i barwę --brand.
W przeciwieństwie do color-mix() zmienia dokładnie jedną właściwość: obniża jasność bez ruszania chromy, obraca barwę bez zmiany jasności albo dodaje alfę do koloru zapisanego jako hex.
| Przepis | CSS | Wynik z #2563eb | Biały tekst |
|---|---|---|---|
| Ciemniejszy | oklch(from var(--brand) calc(l - 0.1) c h) | #0442c8 | 8.02:1 |
| Jaśniejszy | oklch(from var(--brand) calc(l + 0.1) c h) | #4484ff | 3.51:1 |
| Połowa chromy | oklch(from var(--brand) l calc(c * 0.5) h) | #4e6faf | 4.98:1 |
| Barwa +30° | oklch(from var(--brand) l c calc(h + 30)) | #7a49df | 5.46:1 |
| Komplementarny | oklch(from var(--brand) l c calc(h + 180)) | #956700 | 4.98:1 |
| Szary, ta sama jasność | oklch(from var(--brand) l 0 h) | #707070 | 4.95:1 |
| Alfa 50% | oklch(from var(--brand) l c h / 0.5) | rgba(37, 99, 235, 0.5) | Zależy od tła |
CSS
:root {
--brand: #2563eb;
--brand-hover: oklch(from var(--brand) calc(l - 0.08) c h);
--brand-muted: oklch(from var(--brand) l calc(c * 0.5) h);
--brand-ring: oklch(from var(--brand) l c h / 0.4);
--accent: oklch(from var(--brand) l c calc(h + 30));
}Spójrz na cztery wiersze, które zachowują l: zmiana chromy lub barwy przesuwa kontrast białego tekstu tylko w zakresie od 4,95:1 do 5,46:1, wobec 5,17:1 dla oryginału. O kontraście decyduje jasność, więc warianty barwy zbudowane w ten sposób zachowują mniej więcej tę samą dostępność. Kolor jaśniejszy i komplementarny przy pełnej chromie wypadają poza sRGB, a przeglądarka mapuje je na najbliższy kolor, jaki potrafi wyświetlić.
color-mix() a kolory względne a Sass darken()
Używaj color-mix(), gdy myślisz kategoriami „trochę czerni” albo „trochę przezroczystości”. Używaj składni kolorów względnych, gdy myślisz kanałami: „ten sam kolor, o 0,1 ciemniejszy”. Obie zastępują większość funkcji kolorów z Sass i nadążają za zmianą motywu bez przebudowy.
| color-mix() | Składnia kolorów względnych | Sass darken() | |
|---|---|---|---|
| Gdzie działa | W przeglądarce | W przeglądarce | Podczas budowania |
| Działa z var() i currentColor | Tak | Tak | Nie |
| Model koloru | Przestrzeń, którą podasz: srgb, oklab, oklch, hsl i inne | Dowolna funkcja koloru: oklch(), rgb(), hsl(), lab() | Tylko HSL |
| Najlepsze do | Rozjaśnień, przyciemnień, przezroczystości, mieszania dwóch tokenów | Zmiany jednego kanału: jasności, chromy, barwy lub alfy | Starego kodu ze stałymi kolorami |
| Wielkość kroku | Część odległości do drugiego koloru | Dokładna, w jednostkach kanału | Stałe punkty HSL, nierówne dla oka |
| Obsługa od | Chrome 111, Safari 16.2, Firefox 113 | Chrome 119, Safari 18, Firefox 128 | Każda przeglądarka, wynik jest statyczny |
Dlaczego Sass darken() daje nierówne kroki
darken($color, 10%) odejmuje 10 punktów jasności HSL. Jasność HSL to nie postrzegana jasność, więc widoczna zmiana zależy od barwy. Oto darken(10%) obok oklch(from … calc(l - 0.1) c h) na czterech popularnych kolorach interfejsu, zmierzone przez ΔE OK, gdzie około 0,02 to najmniejsza różnica, jaką da się zobaczyć.
| Kolor | Sass darken(10%) | ΔE OK | OKLCH l − 0.1 | ΔE OK |
|---|---|---|---|---|
| #2563eb | #134cca | 0.078 | #0442c8 | 0.099 |
| #facc15 | #d7ad05 | 0.099 | #d9ab00 | 0.101 |
| #16a34a | #107636 | 0.136 | #00842b | 0.094 |
| #dc2626 | #b21d1d | 0.090 | #b70000 | 0.089 |
To samo darken(10%) zmienia zieleń 1,7 raza mocniej niż niebieski. Wersja OKLCH mieści się między 0,089 a 0,101 dla każdej barwy; niewielki rozrzut bierze się z mapowania gamutu, bo niektóre ciemniejsze kolory wypadają tuż poza sRGB. Sam Sass zaleca color.adjust() lub color.scale() zamiast darken(), a w czystym CSS nie potrzebujesz żadnej z nich.
Obsługa w przeglądarkach i fallbacki
color-mix() działa w Chrome i Edge 111, Safari 16.2 oraz Firefox 113, wydanych w 2023 roku. Składnia kolorów względnych pojawiła się w Chrome i Edge 119 oraz Firefox 128; Safari wprowadziło wczesną wersję w 16.4, a obecną składnię w Safari 18. Wszystkie aktualne przeglądarki obsługują obie.
Popularny trik ze zwykłą wartością w linii powyżej nie działa, gdy nowa wartość zawiera var(). Deklaracja z var() jest zawsze akceptowana na etapie parsowania, więc przeglądarka odrzuca twoją linię zapasową, a jeśli nie potrafi policzyć color-mix(), wraca do wartości dziedziczonej lub początkowej. Zamiast tego opakuj nowoczesną wersję w @supports.
CSS
.button:hover {
background: #1f55cc;
}
@supports (color: color-mix(in oklab, red, blue)) {
.button:hover {
background: color-mix(in oklab, var(--brand) 90%, black);
}
}
@supports (color: oklch(from red l c h)) {
.badge {
border-color: oklch(from var(--brand) l c h / 0.3);
}
}Pytania i odpowiedzi
Co robi color-mix() w CSS?
color-mix() miesza dwa kolory w podanej przestrzeni barw i zwraca wynik, na przykład color-mix(in oklab, var(--brand) 90%, black) daje kolor o 10% ciemniejszy od koloru marki. Działa w przeglądarce, więc współpracuje ze zmiennymi CSS i currentColor.
Jakiej przestrzeni barw używać w color-mix()?
Do rozjaśnień, przyciemnień i stanów hover używaj oklab, bo daje równe kroki jasności i zachowuje barwę. Oklch wybierz przy mieszaniu dwóch nasyconych kolorów, gdy chcesz nasyconego wyniku, a srgb przy mieszaniu z transparent. Niebieski i żółty zmieszane w srgb dają płaską szarość #808080, a w oklch intensywny morski turkus.
Jak przyciemnić kolor na hover w czystym CSS?
Użyj background: color-mix(in oklab, var(--brand) 90%, black) albo background: oklch(from var(--brand) calc(l - 0.1) c h). Dla #2563eb pierwszy daje #1f55cc, a drugi #0442c8, podnosząc kontrast białego tekstu z 5,17:1 do 6,50:1 i 8,02:1.
Jak zrobić przezroczysty kolor ze zmiennej CSS?
Napisz color-mix(in srgb, var(--brand) 20%, transparent) albo oklch(from var(--brand) l c h / 0.2). Oba działają, gdy --brand jest zapisany jako hex, i nie zmieniają samego koloru, obniżają tylko jego krycie.
Czy color-mix() działa we wszystkich przeglądarkach?
Tak, obsługują go wszystkie aktualne przeglądarki: Chrome i Edge od wersji 111, Safari od 16.2 i Firefox od 113. Dla starszych przeglądarek wpisz najpierw statyczny kolor, a wersję z color-mix() umieść w @supports, bo linia z var() nie potrafi sama wrócić do wartości zapasowej.
Czym różni się color-mix() od składni kolorów względnych?
color-mix() miesza dwa kolory w podanych proporcjach, co pasuje do rozjaśnień, przyciemnień i przezroczystości. Składnia kolorów względnych, jak oklch(from var(--brand) l c calc(h + 30)), przepisuje poszczególne kanały jednego koloru, co pasuje do dokładnych kroków jasności, przesunięć barwy i alfy.