Bytte font farge for ulike bakgrunner med CSS

0
10

Noen gang få en av disse, “jeg kan gjøre det med CSS!” øyeblikk mens du ser på noen flex JavaScript muskler? Det er akkurat den følelsen jeg fikk når du ser på en Dag-Inge Aas & Ida Aalen snakke om CSSconf EU-2018.

De er basert i Norge, hvor WCAG tilgjengelighet er ikke bare god praksis, men som faktisk kreves av loven (gå, Norge!). Som de var å utvikle en funksjon som lar brukeren-valgbar farge tematisere for deres viktigste produkt, sto de overfor en utfordring: automatisk justere font color basert på den valgte bakgrunnsfargen av beholderen. Dersom bakgrunnen er mørk, da ville det være ideelt å ha en hvit tekst for å holde det WCAG kontrast-kompatibel. Men hva skjer hvis en lys bakgrunnsfarge er valgt i stedet? Teksten er både uleselig og mislykkes tilgjengelighet.

De brukte en elegant tilnærming og løste problemet ved å bruke “farge” npm-pakke, legge til betinget grenser og automatisk sekundær farge generasjon mens de var på det.

Men det er en JavaScript-løsning. Her er min pure CSS alternativ.

Utfordringen

Her er kriteriene jeg har satt ut for å oppnå:

  • Endre skriftfargen til enten svart eller hvitt, avhengig av bakgrunnsfarge
  • Gjelder samme slags logikk til grenser, med en mørkere variant av base fargen på bakgrunnen for å forbedre knappen synlighet, bare hvis bakgrunnen er veldig lys
  • Automatisk generere en sekundær, 60º hue-rotert farge

Arbeide med HSL farger og CSS variabler

Den enkleste tilnærmingen jeg kunne tenke dette innebærer kjører HSL farger. Innstillingen bakgrunnen erklæringer som HSL hvor hver parameter er en egendefinert CSS-egenskapen gir mulighet for en virkelig enkel måte å avgjøre letthet og bruke den som base for våre betinget utsagn.

:root {
–fargetone: 220;
–lør: 100;
–lys: 81;
}

.btn {
bakgrunn: hsl(var(–fargetone), calc(var(–sat) * 1%), calc(var(–lys) * 1%));
}

Dette bør tillate oss å bytte bakgrunnen til alle slags farger vi vil under kjøring ved å endre variabler og kjører en if/else-setningen til å endre forgrunnsfargen.

Bortsett fra… vi ikke har if/else-setninger på CSS… eller gjør vi?

Innføring i CSS betinget uttalelser

Siden introduksjonen av CSS variabler, vi fikk også betinget utsagn for å gå med dem. Eller liksom.

Dette trikset er basert på det faktum at noen CSS parametre få avkortet til en min-og max-verdi. For eksempel, tenk dekkevne. Gyldig utvalg er fra 0 til 1, så vi normalt holde den der. Men vi kan også erklære en tetthet av 2, 3, eller 1000, og det vil være begrenset oppad til 1 og tolket som slike. På en lignende måte, vi kan til og med erklærer en negativ opacity verdi, og få det begrenset oppad til 0.

.noe {
dekkevne: -2; /* løser til 0, transparent */
dekkevne: -1; /* løser til 0, transparent */
dekkevne: 2; /*løser til 1, helt ugjennomsiktig */
dekkevne: 100; /* løser til 1, helt ugjennomsiktig */
}

Påfører kunsten til vår font color erklæring

Letthet parameter av en HSL farge erklæring oppfører seg på en lignende måte, tildekking eventuelle negative verdien til 0 (som resulterer i svart, uansett kulør og metning skjer for å være), og noe over 100% er begrenset oppad til 100% (som alltid er hvit).

Så, vi kan erklære farge som HSL, subtrahere ønsket terskelen fra letthet parameteren, deretter multiplisere med 100% for å tvinge den til å gå over en av grensene (enten sub-null eller høyere enn 100%). Siden vi trenger negative resultater for å løse i hvit og positive resultater for å løse i svart, er vi også nødt til å snu det å multiplisere resultatet med -1.

:root {
–lys: 80;
/* terskelen på hvilke farger som er ansett som “lys”. Område: heltall fra 0 til 100,
anbefales 50 – 70 */
–terskel: 60;
}

.btn {
/* Noen letthet verdi er lavere enn terskelen vil resultere i hvitt, noen over vil resultere i svart */
–bryter: calc((var(–lys) – var(–terskel)) * -100%);
farge: hsl(0, 0%, var(–bryteren));
}

La oss se litt på koden: fra en letthet av 80 og vurderer en 60 terskel, subtraksjon resultater i 20, som multiplisert med -100%, resulterer i -2000 ma% begrenset oppad til 0%. Vår bakgrunn er lettere enn den grensen, så vi anser det lett og bruke svart tekst.

Hvis vi hadde satt –lys variabel som 20, subtraksjon ville ha resultert i -40, som multiplisert med -100% ville slå 4000%, begrenset oppad til 100%. Våre lys variabel er lavere enn terskelen, derfor anser vi det som et “mørkt” bakgrunnen og bruke hvit tekst for å holde en høy kontrast.

Generere en betinget grensen

Når bakgrunnen av et element blir for lys, kan det bli lett tapt mot en hvit bakgrunn. Kanskje vi har en knapp og ikke engang merke til det. For å gi et bedre BRUKERGRENSESNITT på virkelig lyse farger, kan vi sette en grense basert på de samme bakgrunnsfarge, bare mørkere.

En lys bakgrunn med en mørk kant basert på at bakgrunnsfargen.

For å oppnå dette, kan vi bruke samme teknikk, men gjelder det å alfakanalen av en HSLA-erklæringen. På den måten kan vi justere farge som trengs, så har enten fullstendig gjennomsiktige eller fullstendig ugjennomsiktige.

:root {
/* (…) */
–lys: 85;
–grensen-grensen: 80;
}

.btn {
/* setter grensen-farge som en 30% mørkere nyanse av samme farge*/
–grensen-lys: calc(var(–lys) * 0.7%);
–grensen-alfa: calc((var(–lys) – var(–grensen-grensen)) * 10);

grensen: .1em solid hsla(var(–fargetone), calc(var(–sat) * 1%), var(–grensen-lys), var(–grensen-alfa));
}

Forutsatt en nyanse av 0 og metning i 100% koden ovenfor vil gi en fullstendig ugjennomsiktig, ren rød kant på 70% opprinnelige letthet hvis bakgrunnen letthet er høyere enn 80, eller en fullt gjennomsiktig grensen (og derfor ingen grenser i det hele tatt) hvis det er mørkere.

Innstilling av sekundær, 60º hue-rotert farge

Trolig den enkleste av utfordringer. Det er to mulige tilnærminger for det:

  1. filter: hue-rotate(60): Dette er den første som kommer til hjernen, men det er ikke den beste løsningen, da det vil påvirke fargen på barnet elementer. Hvis nødvendig, kan det bli reversert med en motsatt rotasjonsretning.
  2. HSL nyanse + 60: Det andre alternativet er å få våre hue variabel, og å legge til 60 til det. Siden hue parameteren har ikke begrensninger i atferd på 360, men i stedet legger seg rundt (som noen CSS <vinkel> skriv gjør), bør det fungere uten problemer. Tror 400deg=40deg, 480deg=120deg, etc.

Vurderer dette, kan vi legge til en modifikator klasse for våre sekundær-fargede elementer som legger til 60 nyansen verdi. Siden self-modifisering av variabler er ikke tilgjengelig i CSS (dvs. det er ingen slike ting som –fargetone: calc(var(–fargetone) + 60) ), kan vi legge til et nytt aux-variabel for våre hue manipulasjon til vår base stil og bruk den i bakgrunnen, og grensen erklæring.

.btn {
/* (…) */
–h: var(–fargetone);
bakgrunn: hsl(var(–h), calc(var(–sat) * 1%), calc(var(–lys) * 1%));
grensen:.1em solid hsla(var(–h), calc(var(–sat) * 1%), var(–grensen-lys), var(–grensen-alfa));
}

Deretter re-erklære sin verdi i vår modifier:

.btn–videregående {
–h: calc(var(–fargetone) + 60);
}

Beste om denne tilnærmingen er at det vil automatisk tilpasse alle våre beregninger til den nye hue og bruke dem til egenskaper, på grunn av CSS egendefinerte egenskaper scoping.

Og det vi er. Å sette det hele sammen, her er min pure CSS-løsning til alle de tre utfordringene. Denne skal fungere som en sjarm og redde oss fra, inkludert en ekstern JavaScript-bibliotek.

Se Penn CSS Automatisk bytte font color avhengig element bakgrunn…. MISLYKKES av Facundo Corradini (@facundocorradini) på CodePen.

Bortsett fra det har det ikke. Noen nyanser bli veldig problematisk, spesielt for gule, og cyans), som de vises måte lysere enn andre (f.eks. røde og blues) til tross for å ha samme letthet verdi. I følge noen farger er behandlet som mørk og gitt hvit tekst til tross for at svært lyse.

Hva i navnet av CSS er det som skjer?

Innføring oppfattet letthet

Jeg er sikker på at mange av dere kanskje har lagt merke til at det langt foran, men for resten av oss, slår ut letthet vi oppfatter er ikke det samme som HSL letthet. Heldigvis har vi noen metoder for å veie i hue letthet og tilpasse våre koden slik at den reagerer på hue, så vel.

For å gjøre det, må vi ta hensyn til det som oppfattes letthet av de tre primære fargene ved å gi hver en koeffisient som tilsvarer hvor lyse eller mørke de menneskelige øyet oppfatter det. Dette er vanligvis referert til som luma.

Det er flere metoder for å oppnå dette. Noen er bedre enn andre i spesifikke tilfeller, men ikke er 100% perfekt. Så, jeg valgte de to mest populære, som er gode nok:

  • sRGB Luma (ITU Rec. 709): L = (rød * 0.2126 + grønn * 0.7152 + blå * 0.0722) / 255
  • W3C-metoden (working draft): L = (rød * 0.299 + grønn * 0.587 + blå * 0.114) / 255

Implementering av luma-korrigert beregninger

Den første åpenbare implikasjoner fra å gå med en luma-korrigert tilnærming er at vi ikke kan bruke HSL, siden CSS ikke har native metoder for å få tilgang til RGB-verdiene for en HSL-erklæringen.

Så, vi må bytte til en RBG-erklæring for bakgrunn, beregne luma fra hvilken metode vi velger, og bruke det på vår forgrunnsfargen erklæringen, som kan (og vil) fortsatt være HSL.

:root {
/* tema farge variabler til bruk i RGB-deklarasjoner */
–red: 200;
–grønn: 60;
–blå: 255;
/* terskelen på hvilke farger som er ansett som “lys”.
Rekkevidde: antall desimaler fra 0 til 1, anbefales 0.5 – 0.6 */
–terskel: 0.5;
/* terskelen for når en mørkere grensen vil bli brukt.
Rekkevidde: antall desimaler fra 0 til 1, anbefales 0.8+ */
–grensen-grensen: 0.8;
}

.btn {
/* setter bakgrunnen for base klasse */
bakgrunn: rgb(var(–rød), var(–grønn), var(–blå));

/* beregner oppfattet letthet bruke sRGB Luma metode
Luma = (rød * 0.2126 + grønn * 0.7152 + blå * 0.0722) / 255 */
–r: calc(var(–rød) * 0.2126);
–g: calc(var(–grønn) * 0.7152);
–b: calc(var(–blå) * 0.0722);
–sum: calc(var(–r) + var(–g) + var(–b));
–oppfattet-lysstyrke: calc(var(–sum) / 255);

/* viser enten hvit eller svart farge avhengig oppfattet mørket */
farge: hsl(0, 0%, calc((var(–oppfattet-lightness) – var(–terskel)) * -10000000%));
}

For betinget grenser, vi må slå erklæring til en RGBA, og igjen, bruke alpha-kanal for å gjøre det enten fullstendig gjennomsiktige eller fullstendig ugjennomsiktige. Ganske mye det samme som før, bare kjører RGBA i stedet for HSLA. Den mørkere nyanse er oppnådd ved halvering hver fargekanal.

.btn {
/* (…) */
/* gjelder en mørkere kant hvis letthet er høyere enn grensen terskelen */
–grensen-alfa: calc((var(–oppfattet-lightness) – var(–grensen-grensen)) * 100);
grensen: .2em solid rgba(calc(var(–rød) * 0.5), calc(var(–grønn) * 0.5), calc(var(–blå) * 0.5), var(–grensen-alfa));
}

Siden vi mistet vår første HSL bakgrunn erklæring, vår sekundær farge-tema må være innhentet via hue rotasjon:

.btn–videregående {
filter: hue-rotate(60deg);
}

Dette er ikke den beste tingen i verden. I tillegg til å anvende hue rotasjon til potensielle barn elementer som tidligere omtalt, betyr det at de vil bytte til svart/hvitt og grensen synlighet på sekundært element, vil avhenge av de viktigste element er hue, og ikke på sin egen. Men så vidt jeg kan se, JavaScript gjennomføringen har det samme problemet, så jeg vil kalle det nær nok.

Og der har vi det, denne gangen for godt.

Se Penn CSS Automatisk WCAG kontrast font-farge avhengig element bakgrunn av Facundo Corradini (@facundocorradini) på CodePen.

En ren CSS-løsning som oppnår den samme effekten som den opprinnelige JavaScript tilnærming, men betydelig kutt på plass.

Nettleserstøtte

IE er utelukket på grunn av bruk av CSS variabler. Edge har ikke begrensninger i atferd vi brukte hele. Det ser erklæring, mener det er tull, og forkaster det helt som det skulle noen ødelagte/ukjent regelen. Alle andre store leseren skal fungere.