HEX, RGB, HSL och de nyare färgrymderna
En färg i CSS är tre eller fyra tal plus en regel för hur de ska läsas: färgrymden. Paletter som ser ojämna ut på skärmen är oftast stegade i en rymd som inte matchar hur synen fungerar.
HEX och RGB är samma tal
#3B82F6 är tre bytes i bas 16: 3B är 59, 82 är 130, F6 är 246, så rgb(59, 130, 246) är samma färg i bas 10, och en färgkonverterare rör sig mellan dem utan förlust.
Kortformen med tre siffror dubblerar varje siffra i stället för att fylla ut den, så #F80 expanderar till #FF8800, inte #F08000; bara 4 096 färger har en kortform. #RGBA lägger till en alfasiffra på samma sätt och #RRGGBBAA skriver ut alla fyra byten, där alfabyten löper 0 till 255 (1A är 10 %, 80 är 50 %, CC är 80 %) medan alla andra alfavärden i CSS löper 0 till 1. Modern syntax tar också komponenter separerade med mellanslag och alfa efter ett snedstreck: rgb(59 130 246 / 50%).
Dessa tal är inte ljusmängder. sRGB kodar varje kanal genom en överföringskurva på ungefär 2,2, så 128 avger omkring 21,6 % av det ljus som 255 gör, inte hälften, och varje blandning som räknas ut på råvärdena ärver den kurvan.
HSL, HSV och ljusheten som inte är ljushet
HSL omarrangerar samma RGB-kub till en cylinder: nyansen är en vinkel (0 rött, 120 grönt, 240 blått), mättnaden är avståndet från den centrala grå axeln, och ljusheten löper från 0 via 50 %, den rena nyansen, till 100 %, vitt. HSV byter ljushet mot värde, vars topp är den rena nyansen i stället för vitt, så ljusare varianter kommer av sänkt mättnad. Ingen av dem vet något om perception.
| Färg | HSL | Relativ luminans | OKLCH-ljushet |
|---|---|---|---|
#FFFF00 | hsl(60 100% 50%) | 0,928 | 0,97 |
#0000FF | hsl(240 100% 50%) | 0,072 | 0,45 |
Båda hävdar 50 % ljushet; den ena avger omkring tretton gånger så mycket ljus som den andra. En rad tokens där varje nyans är fastnaglad vid 50% läses som ett virrvarr av tyngder.
Att stega HSL-ljusheten vid fast nyans och mättnad misslyckas av samma skäl, plus ett till: mättnaden mäts mot det utrymme som finns vid den ljusheten, så steget på 50 % är det färgstarkaste och allt på ömse sidor tunnas ut. Nyansvinkeln står också stilla medan det upplevda kromat kollapsar, vilket är det som får mellanstegen att se grumliga ut. De är inte grundfärgen förmörkad, de är grundfärgen gråad med en mängd ingen valde.
Gult visar det värsta av det. Att sänka HSL-ljusheten går rakt från gult till olivgrönt, eftersom ett mörkt gult vid den nyansvinkeln helt enkelt är olivgrönt, och att hålla det gult kräver att nyansen roteras mot orange på vägen ner. Blått har det motsatta problemet och ligger så lågt i luminans att hela dess användbara variation finns ovanför 50 %.
LCH och OKLCH
LCH är polärformen av CIE Lab, byggd på färgmatchningsexperiment: ljushet 0 till 100, kroma (färgstyrka, utan fast maximum) och en nyansvinkel. OKLCH har samma form fast över Oklab, vilket rättar till de nyansförskjutningar Lab är känt för, mest synligt att blått driver mot lila när det ljusnar.
.a { color: oklch(0.62 0.21 260); } /* L 0 to 1, C about 0 to 0.37, H in degrees */
.b { color: oklch(62% 0.21 260 / 50%); } /* percentages and alpha both allowed */
.c { color: lch(52% 82 285); } /* L runs 0 to 100 here */
Perceptuell likformighet betyder att lika numeriskt avstånd ligger nära lika upplevt avstånd. En ramp i steg om 0,08 ljushet ser ut som jämna steg, en rad nyanser fastnaglade vid en enda ljushet läses som en rad, och att ändra ljusheten lämnar nyansen där du satte den. Interpolationen kan också använda rymden, som i linear-gradient(in oklch, #0000FF, #FFFF00). En varning: OKLCH-ljushet ligger mycket närmare upplevd ljusstyrka än HSL, men det är inte WCAG:s luminansformel, så textpar går fortfarande genom en kontrastkontroll.
Färgomfång och klippning
Ett färgomfång är den mängd färger en rymd kan representera. sRGB är den sedan länge etablerade standarden; Display P3 täcker ungefär en fjärdedel mer av det synliga färgrummet, och nästan hela vinsten ligger i djupa röda och gröna toner. Kromat i OKLCH är obegränsat, så det är lätt att skriva en färg som ingen skärm kan visa.
@supports (color: color(display-p3 1 1 1)) {
.vivid { background: color(display-p3 0.9 0.1 0.14); }
}
Klippning per kanal drar tillbaka en färg utanför omfånget genom att spärra varje kanal vid sin gräns oberoende av de andra, vilket förskjuter nyansen och plattar ut näraliggande toner till ett enda värde, så att gradienter bandas och detaljer försvinner. CSS Color 4 mappar i stället: håll ljushet och nyans, sänk kromat i OKLCH tills färgen ryms.
Det är därför en skärmbild av ett färgstarkt foto kan se platt ut. Fotot bär en P3-profil och skärmen visar det fullt ut; en export konverterad till sRGB pressar in de mättade pixlarna i en mindre volym, och röda och gröna toner har längst väg att gå.
Alfakomposition
En halvgenomskinlig färg över en bakgrund ger en tredje, ogenomskinlig färg, en kanal i taget:
result = alpha * source + (1 - alpha) * backdrop
Svart på 50 % över vitt ger (127.5, 127.5, 127.5), alltså #808080. Det sammanträffandet är varifrån "svart med 50 % opacitet är grått" kommer, och det håller på vitt och ingen annanstans: samma svarta över #3B82F6 ger ungefär #1E417B, ett mörkt blått, och över ett fotografi ger det en annan färg i varje pixel. Halvgenomskinligt svart är en förmörkningsoperator, inte ett grått.
Kompositionen körs på de kodade värdena, så det där #808080 har en relativ luminans på omkring 0,216 snarare än 0,5. Mät den sammansatta hex-koden, aldrig den deklarerade färgen. Alfa staplas inte heller som väntat: två lager på 50 % täcker 75 %, eftersom det andra bara verkar på det som det första släppte igenom.
Tint, shade, tone och harmonier
| Term | Traditionellt recept | I OKLCH |
|---|---|---|
| Tint | grundfärg plus vitt | höj L, sänk C något |
| Shade | grundfärg plus svart | sänk L |
| Tone | grundfärg plus grått | sänk C vid fast L |
Harmonier är nyansförskjutningar. De väljer en accent; de bygger inget system.
| Harmoni | Förskjutningar | Bra för |
|---|---|---|
| Komplementär | +180° | en accent mot en dominerande nyans, fast den vibrerar när båda är mättade |
| Analog | ±30° | lugna grupperingar, bakgrunder, tillstånd inom en familj |
| Triadisk | +120°, +240° | kategoriska uppsättningar som diagramserier |
Förskjutningar på RGB-hjulet är inte perceptuellt jämna, eftersom det hjulet ägnar en oproportionerlig båge åt grönt, så behandla en uträknad harmoni som ett första utkast och justera med ögat. En palettgenerator som bygger tinter, shader och harmonier ur en grundfärg får upp det utkastet på skärmen snabbt.
Den neutrala skalan kräver ett eget beslut. Ett rent grått, med rött, grönt och blått lika, ser dött och svagt smutsigt ut bredvid ett färgat gränssnitt. Ge neutralerna ett litet kroma, ungefär 0,005 till 0,02 i OKLCH, nära varumärkets nyans: varma gråtoner kring 60° till 90° läses som papper, kalla gråtoner kring 250° läses som tekniska. Trappa ner det kromat mot de ljusaste stegen så att bakgrunder inte ser tonade ut.
Färgblindhet
Omkring 1 av 12 män och 1 av 200 kvinnor har en färgseendedefekt. Röd-gröna typer står för nästan allt, med deuteranomali med bred marginal; blå-gula tritantyper drabbar färre än 1 av 10 000.
Förväxlingarna som spelar roll i gränssnitt är rött mot grönt vid liknande ljushet, grönt mot brunt och orange, lila mot blått, och rosa mot grått. Protanseende gör dessutom röda toner mörkare, så ett rött valt för att signalera brådska kan sluta som det mest anspråkslösa på skärmen.
Färg får därför aldrig vara den enda bäraren av betydelse. Para varje tillstånd med en etikett, en ikon, en form eller en position, och låt parade tillstånd skilja sig i ljushet såväl som i nyans, eftersom ett rött och ett grönt med matchande OKLCH-ljushet är den sämsta tänkbara kombinationen. Att konvertera en skärmbild till gråskala fångar de krockarna på några sekunder.