Byta teckenfärg för olika bakgrunder med CSS

0
48

Någonsin få en av dessa, “jag kan göra det med CSS!” ögonblick medan du tittar på någon flex deras JavaScript muskler? Det är precis den känslan jag fick när du tittar på Dag-Inge Aas & Ida Aalen prata på CSSconf EU: s 2018.

De är baserade i Norge, där WCAG tillgänglighet är inte bara god praxis, men som faktiskt krävs av lagen (gå, Norge!). Som de höll på att utveckla en funktion som gör att användaren kan välja färg teman för deras huvudsakliga produkt, de stod inför en utmaning: att automatiskt justera typsnittsfärg, baserad på den valda bakgrundsfärgen i behållaren. Om bakgrunden är mörk, så det skulle vara perfekt att ha en vit text för att hålla det WCAG kontrast-kompatibel. Men vad händer om en ljus bakgrundsfärg är valt istället? Texten är både oläsligt och misslyckas tillgänglighet.

De använde en elegant metod och löst problemet med hjälp av “färg” i npm paket, lägga till villkorlig gränser och automatisk sekundär färg generation medan de var på det.

Men det är en JavaScript-lösning. Här är min ren CSS alternativ.

Utmaningen

Här är de kriterier som jag satt ut för att utföra:

  • Ändra teckensnitt färg till antingen svart eller vitt beroende på bakgrundsfärg
  • Tillämpar samma typ av logik för att gränser, med hjälp av en mörkare variant av bas färg på bakgrunden för att förbättra knappen synlighet, endast om bakgrunden är riktigt ljus
  • Automatiskt generera en sekundär, 60º hue-roteras färg

Att arbeta med HSL färger och CSS variabler

Det enklaste sättet jag kunde tänka för detta innebär att man kör HSL färger. Inställning bakgrund förklaringar som HSL där varje parameter är en CSS-anpassade egenskapen gör det möjligt för en riktigt enkelt sätt att avgöra lätthet och använda det som bas för vår villkorssatser.

:root {
–nyans: 220;
–lör: 100;
–bakgrund: 81;
}

.btn {
bakgrund: hrt(var(–nyans), calc(var(–lör) * 1%), calc(var (- ljus) * 1%));
}

Detta bör göra det möjligt för oss att byta bakgrund till vilken färg vi skulle vilja vid körning genom att ändra variablerna och kör en if/else-sats för att ändra förgrundsfärgen.

Utom… vi har inte if/else-satser på CSS… eller gör vi?

Att införa CSS villkorssatser

Sedan införandet av CSS variabler, som vi också fick villkorliga satser för att gå med dem. Eller typ.

Detta trick är baserat på det faktum att vissa CSS-parametrar få utjämnade till ett min och max värde. Till exempel, tror opacitet. Giltigt intervall är från 0 till 1, så vi brukar hålla det där. Men vi kan också förklara en opacitet om 2, 3 eller 1000, och det kommer att vara begränsat till 1 och tolkas som sådana. På ett liknande sätt, vi kan även förklara en negativ opacitet värde, och få det begränsat till 0.

.något {
opacitet: -2; /* löser till 0, transparent */
opacitet: -1; /* löser till 0, transparent */
opacitet: 2; /*beslutar att 1, helt ogenomskinlig */
opacitet: 100; /* beslutar att 1, helt ogenomskinlig */
}

Tillämpa trick för att våra font color förklaring

Den lätthet parametern för en HRT färg förklaring beter sig på ett liknande sätt, tak för eventuella negativa värdet till 0 (vilket resulterar i svart, oavsett nyans och mättnad råkar vara) och något över 100% är maximerad till 100% (vilket alltid är vita).

Så, vi kan förklara den färg som HSL, subtrahera den önskade tröskelvärdet från lätthet parameter, för att sedan multiplicera med 100% för att tvinga den att skjuta en av de gränser (antingen sub-zero eller högre än 100%). Eftersom vi behöver negativa resultat att lösa i vitt och positiva resultat för att lösa i svart, som vi också har att invertera det multiplicera resultatet med -1.

:root {
–bakgrund: 80;
/* tröskeln på vilka färger som anses vara “ljus”. Utbud: heltal från 0 till 100,
rekommenderas 50 – 70 */
–tröskel: 60;
}

.btn {
/* Alla lätthet värde understiger det tröskelvärde som kommer att resultera i vitt, någon ovan kommer att resultera i svart */
–växel: calc((var(–ljus) – var(–tröskel)) * -100 procent).
färg: hsl(0, 0%, var(–byt));
}

Låt oss granska det lite kod: från och med en lätthet av 80 och funderar på en 60 tröskeln, subtraktion resultat 20, som multipliceras med -100%, resultat i -2000% utjämnade till 0%. Vår bakgrund är lättare än tröskeln, så vi anser att det är lätt och applicera svart text.

Om vi hade satt-ljus variabel som 20, subtraktion skulle ha resulterat i -40, som multipliceras med -100% skulle vända 4000%, som är maximerad till 100%. Vår ljus-variabeln är mindre än tröskeln, därför anser vi att det är en “mörk” bakgrund och tillämpa vit text för att hålla en hög kontrast.

Skapa en villkorlig gränsen

När bakgrunden av ett element blir för ljus, det kan bli lätt förlorade mot en vit bakgrund. Vi kan ha en knapp och inte ens märker det. För att ge en bättre UI på riktigt ljusa färger, kan vi sätta en gräns baserad på samma bakgrundsfärg, bara mörkare.

En ljus bakgrund med mörk ram som bygger på att färg på bakgrunden.

För att uppnå detta kan vi använda samma teknik, men att tillämpa det alfakanal av en HSLA förklaring. På det sättet kan vi justera färg som behövs, sedan har antingen helt öppet eller helt ogenomskinlig.

:root {
/* (…) */
–bakgrund: 85;
–border tröskel: 80;
}

.btn {
/* sätter gränsen-färg som 30% mörkare nyans av samma färg*/
–border ljus: calc(var (- ljus) * 0.7 procent).
–border-alpha: calc((var(–ljus) – var(–border tröskel)) * 10);

gränsen: .1em fast hsla(var(–nyans), calc(var(–lör) * 1%), var(–border ljus), var(–border-alpha));
}

Om man antar en nyans av 0 och mättnad på 100%, ovanstående kod kommer att ge en helt ogenomskinlig, ren röd gränsen vid 70% av den ursprungliga lätthet om bakgrunden lätthet är högre än 80, eller en helt öppen gränsen (och därför ingen kant alls) om det är mörkare.

Inställningen för den sekundära, 60º hue-roteras färg

Förmodligen den enklaste av utmaningar. Det finns två möjliga metoder för det:

  1. filter: hue-rotera(60): Detta är den första som kommer att tänka på, men det är inte den bästa lösningen, eftersom det skulle påverka färgen på den underordnade element. Om det behövs, det kan vändas med en motsatt rotation.
  2. HSL nyans + 60: Det andra alternativet är att få våra nyans rörliga och lägga 60 till det. Eftersom nyans parametern inte har tak beteende på 360 men istället sveper runt (som alla CSS <vinkeln> typ inte), det bör fungera utan några problem. Tror 400deg=40deg, 480deg=120deg, etc.

Med tanke på detta, vi kan lägga till en modifierare klass för vårt sekundära-färgade element som lägger 60 nyans värde. Eftersom själva ändra variabler är inte tillgängliga i CSS (det vill säga det finns inget sådant som –nyans: calc(var(–nyans) + 60) ), kan vi lägga till en ny extra variabel för våra nyans manipulation till vår bas stil och använder det i bakgrunden och gränsen förklaring.

.btn {
/* (…) */
–h: var(–färg);
bakgrund: hrt(var(–h), calc(var(–lör) * 1%), calc(var (- ljus) * 1%));
gränsen:.1em fast hsla(var(–h), calc(var(–lör) * 1%), var(–border ljus), var(–border-alpha));
}

Sedan åter förklara dess värde i våra modifierare:

.btn–sekundära {
–h: calc(var(–nyans) + 60);
}

Bästa med denna metod är att den automatiskt ska anpassa alla våra beräkningar att den nya nyans och applicera dem på egenskaper, på grund av CSS anpassade egenskaper avgränsning.

Och det är vi. Att sätta ihop allt, här är min ren CSS-lösning för alla tre utmaningarna. Detta bör fungera som en charm och rädda oss från bland annat en extern JavaScript-bibliotek.

Se Pennan CSS Automatisk växla teckensnitt färg beroende på element bakgrund…. MISSLYCKAS med Facundo Corradini (@facundocorradini) på CodePen.

Förutom det inte. Vissa nyanser blir riktigt problematiskt (särskilt gula och cyans), som de visas sättet ljusare än andra (t ex röda och blå) trots att de har samma ljushet värde. I konsekvens med detta, vissa färger behandlas lika mörk och med tanke vit text trots att de är väldigt ljusa.

Vad som i namn av CSS är det som händer?

Att införa upplevda ljusheten

Jag är säker på att många av er kanske har märkt att det är vägen framåt, men för resten av oss, visar den lätthet som vi uppfattar är inte samma sak som HSL lätthet. Lyckligtvis har vi några metoder för att väga i nyans lätthet och anpassa vår kod så att den svarar mot hue.

För att göra detta, behöver vi ta hänsyn till det upplevda ljusheten av de tre primära färgerna genom att ge var och en koefficient, som motsvarar hur ljus eller mörkt det mänskliga ögat uppfattar det. Detta kallas vanligen luma.

Det finns flera metoder för att uppnå detta. Vissa är bättre än andra i vissa fall, men inte är 100% perfekt. Så, har jag valt de två mest populära, som är bra nog:

  • sRGB Luma (ITU Rec. 709): L = (red * 0.2126 + grön * 0.7152 + blå * 0.0722) / 255
  • W3C-metoden (utkast): L = (red * 0.299 + grön * 0.587 + blå * 0.114) / 255

Genomförandet av luma-korrigerade beräkningar

Den första uppenbara innebörden från att gå med en luma-korrigerade synsätt är att vi inte använder HRT, eftersom CSS inte har alternativa metoder för att komma åt RGB-värden i en HRT förklaring.

Så, vi måste byta till en RBG förklaring för bakgrunder, beräkna luma oavsett vilken metod vi väljer, och använda det på vår förgrundsfärgen förklaring, som kan (och kommer) fortfarande vara HSL.

:root {
/* tema färg variabler använda i RGB-förklaringar */
–röd: 200;
–grön: 60;
–blå: 255;
/* tröskeln på vilka färger som anses vara “ljus”.
Utbud: decimaltal mellan 0 och 1, rekommenderas 0.5 – 0.6 */
–tröskel: 0.5;
/* den tröskel vid vilken en mörkare gränsen ska tillämpas.
Utbud: decimaltal mellan 0 och 1, rekommenderas 0.8+ */
–border tröskel: 0.8;
}

.btn {
/* sätter bakgrunden till basklass */
bakgrund: rgb(var(–red), var(–grön), var(–blå));

/* beräknar upplevd lätthet att använda sRGB Luma metod
Luma = (red * 0.2126 + grön * 0.7152 + blå * 0.0722) / 255 */
–r: calc(var(–röd) * 0.2126);
–g: calc(var(–grön) * 0.7152);
–b: calc(var(–blå) * 0.0722);
–summa: calc(var(–r) + var(–g) + var(–b));
–upplevd ljusstyrka: calc(var(–summan) / 255);

/* visar antingen vit eller svart färg beroende på upplevelsen av mörker */
färg: hsl(0, 0%, calc((var(–upplevd ljusstyrka) – var(–tröskel)) * -10000000%));
}

För de villkorade gränser, vi måste vända förklaring till en RGBA, och återigen använda en alfa-kanal för att göra det antingen helt öppet eller helt ogenomskinlig. Ganska mycket samma sak som innan, bara att köra RGBA istället för HSLA. Den mörkare nyans fås genom att halvera varje färgkanal.

.btn {
/* (…) */
/* gäller en mörkare kant om den lätthet är högre än gränsen tröskel */
–border-alpha: calc((var(–upplevd ljusstyrka) – var(–border tröskel)) * 100);
gränsen: .2em fast rgba(calc(var(–röd) * 0.5), calc(var(–grön) * 0.5), calc(var(–blå) * 0.5), var(–border-alpha));
}

Eftersom vi har förlorat våra ursprungliga HSL bakgrund förklaring, vårt sekundära tema färg måste erhållas via hue rotation:

.btn–sekundära {
filter: hue-rotera(60deg);
}

Detta är inte den bästa sak i världen. Utöver att applicera nyansen rotation för att eventuella barn element som tidigare diskuterats, innebär det att byta till svart/vitt och gränsen synlighet på sekundär faktor kommer att bero på den viktigaste delen nyans och inte på sina egna. Men såvitt jag kan se, JavaScript genomförandet har samma problem, så jag kommer att kalla det nära nog.

Och där har vi det, den här gången för gott.

Se Pennan CSS Automatisk WCAG kontrast font-färg beroende på element bakgrund av Facundo Corradini (@facundocorradini) på CodePen.

En ren CSS-lösning som ger samma effekt som originalet med JavaScript, men betydligt nedskärningar på fotavtryck.

Webbläsare-stöd

IE är utesluten på grund av att användningen av CSS variabler. Edge inte har tak beteende som vi använt i hela byggnaden. Det ser förklaring, anser att det är nonsens, och kastas det bort helt och hållet eftersom det skulle brutna/okänd regel. Alla andra större webbläsare ska fungera.