Temme kaskaden med BEM og moderne CSS-velgere PlatoBlockchain Data Intelligence. Vertikalt søk. Ai.

Temme kaskaden med BEM og moderne CSS-velgere

BEM. Som tilsynelatende alle teknikker i verden av frontend-utvikling, skrive CSS i et BEM-format kan være polariserende. Men det er – i hvert fall i min Twitter-boble – en av de mer likte CSS-metodene.

Personlig synes jeg BEM er bra, og jeg synes du bør bruke det. Men jeg forstår også hvorfor du kanskje ikke.

Uavhengig av din mening om BEM, tilbyr den flere fordeler, den største er at den bidrar til å unngå spesifisitetssammenstøt i CSS Cascade. Det er fordi, hvis de brukes riktig, bør alle velgere skrevet i et BEM-format ha samme spesifisitetspoeng (0,1,0). Jeg har laget CSS for mange store nettsteder i løpet av årene (tenk regjeringen, universiteter og banker), og det er på disse større prosjektene jeg har funnet ut at BEM virkelig skinner. Å skrive CSS er mye morsommere når du har tillit til at stilene du skriver eller redigerer ikke påvirker andre deler av nettstedet.

Det er faktisk unntak der det anses som helt akseptabelt å legge til spesifisitet. For eksempel: :hover og :focus pseudoklasser. De har en spesifisitetspoeng på 0,2,0. En annen er pseudoelementer - som ::before og ::after — som har en spesifisitetsscore på 0,1,1. For resten av denne artikkelen, la oss anta at vi ikke vil ha noe annet spesifikt kryp. 🤓

Men jeg er egentlig ikke her for å selge deg på BEM. I stedet vil jeg snakke om hvordan vi kan bruke det sammen med moderne CSS-velgere - tenk :is(), :has(), :where(), etc. — to gain even mer kontroll av kaskaden.

Hva er dette med moderne CSS-velgere?

De CSS Selectors Level 4 spes gir oss noen kraftige nye (ish) måter å velge elementer på. Noen av mine favoritter inkluderer :is(), :where()og :not(), som hver støttes av alle moderne nettlesere og er trygge å bruke på nesten alle prosjekter i dag.

:is() og :where() er i utgangspunktet det samme bortsett fra hvordan de påvirker spesifisiteten. Nærmere bestemt, :where() har alltid en spesifisitetsscore på 0,0,0. Jepp, til og med :where(button#widget.some-class) har ingen spesifisitet. I mellomtiden spesifisiteten til :is() er elementet i argumentlisten med høyest spesifisitet. Så, allerede har vi et kaskade-stridende skille mellom to moderne velgere som vi kan jobbe med.

Den utrolig kraftige :has() relasjonell pseudo-klasse er også raskt få nettleserstøtte (og er den største nye funksjonen i CSS siden Grid, i min ydmyke mening). Men i skrivende stund, nettleserstøtte for :has() er ikke helt god nok for bruk i produksjon ennå.

La meg holde en av de pseudoklassene i BEM-en min og...

/* ❌ specificity score: 0,2,0 */
.something:not(.something--special) {
  /* styles for all somethings, except for the special somethings */
}

Oops! Ser du den spesifisitetspoengsummen? Husk, med BEM ønsker vi ideelt sett at våre velgere skal ha en spesifisitetspoeng på 0,1,0. Hvorfor er 0,2,0 dårlig? Tenk på det samme eksemplet, utvidet:

.something:not(.something--special) {
  color: red;
}
.something--special {
  color: blue;
}

Selv om den andre velgeren er sist i kilderekkefølgen, er den første velgerens høyere spesifisitet (0,2,0) vinner, og fargen på .something--special elementer vil bli satt til red. Det vil si, forutsatt at din BEM er skrevet riktig og det valgte elementet har både .something grunnklasse og .something--special modifikatorklasse brukt på den i HTML-en.

Brukt uforsiktig kan disse pseudoklassene påvirke Cascade på uventede måter. Og det er denne typen inkonsekvenser som kan skape hodepine nedover, spesielt på større og mer komplekse kodebaser.

Dang. Så hva nå?

Husk hva jeg sa om :where() og det faktum at dens spesifisitet er null? Vi kan bruke det til vår fordel:

/* ✅ specificity score: 0,1,0 */
.something:where(:not(.something--special)) {
  /* etc. */
}

Den første delen av denne velgeren (.something) får sin vanlige spesifisitetspoeng på 0,1,0. Men :where() - og alt inni den - har en spesifisitet av 0, som ikke øker spesifisiteten til velgeren ytterligere.

:where() lar oss hekke

Folk som ikke bryr seg så mye om spesifisitet som meg (og det er nok mange, for å være rettferdig) har hatt det ganske bra når det kommer til hekking. Med noen bekymringsløse tastaturtrykk, kan vi ende opp med CSS som dette (merk at jeg bruker Sass for korthets skyld):

.card { ... }

.card--featured {
  /* etc. */  
  .card__title { ... }
  .card__title { ... }
}

.card__title { ... }
.card__img { ... }

I dette eksemplet har vi en .card komponent. Når det er et "fremhevet" kort (ved å bruke .card--featured klasse), må kortets tittel og bilde ha en annen stil. Men som vi vet, koden ovenfor resulterer i en spesifisitetspoengsum som er inkonsistent med resten av systemet vårt.

En hardhendt spesifisitetsnerd kan ha gjort dette i stedet:

.card { ... }
.card--featured { ... }
.card__title { ... }
.card__title--featured { ... }
.card__img { ... }
.card__img--featured { ... }

Det er ikke så ille, ikke sant? Ærlig talt, dette er vakker CSS.

Det er imidlertid en ulempe med HTML. Erfarne BEM-forfattere er sannsynligvis smertelig klar over den klønete mallogikken som kreves for å betinget bruke modifikasjonsklasser på flere elementer. I dette eksemplet må HTML-malen betinget legge til --featured modifikatorklasse til tre elementer (.card, .card__titleog .card__img) men sannsynligvis enda mer i et eksempel fra den virkelige verden. Det er mange if uttalelser.

De :where() selector kan hjelpe oss med å skrive mye mindre mallogikk – og færre BEM-klasser å starte opp – uten å øke spesifisitetsnivået.

.card { ... }
.card--featured { ... }

.card__title { ... }
:where(.card--featured) .card__title { ... }

.card__img { ... }
:where(.card--featured) .card__img { ... }

Her er det samme, men i Sass (merk det etterfølgende og-tegn):

.card { ... }
.card--featured { ... }
.card__title { 
  /* etc. */ 
  :where(.card--featured) & { ... }
}
.card__img { 
  /* etc. */ 
  :where(.card--featured) & { ... }
}

Hvorvidt du bør velge denne tilnærmingen fremfor å bruke modifikasjonsklasser på de forskjellige underelementene er et spørsmål om personlig preferanse. Men i det minste :where() gir oss valget nå!

Hva med ikke-BEM HTML?

Vi lever ikke i en perfekt verden. Noen ganger må du håndtere HTML som er utenfor din kontroll. For eksempel et tredjepartsskript som injiserer HTML som du må style. Denne markeringen er ofte ikke skrevet med BEM-klassenavn. I noen tilfeller bruker ikke disse stilene klasser i det hele tatt, men ID-er!

Igjen :where() har ryggen vår. Denne løsningen er litt hacky, ettersom vi må referere til klassen til et element et sted lenger opp i DOM-treet som vi vet eksisterer.

/* ❌ specificity score: 1,0,0 */
#widget {
  /* etc. */
}

/* ✅ specificity score: 0,1,0 */
.page-wrapper :where(#widget) {
  /* etc. */
}

Å referere til et overordnet element føles imidlertid litt risikabelt og begrensende. Hva om den foreldreklassen endrer seg eller ikke er der av en eller annen grunn? En bedre (men kanskje like hacky) løsning ville være å bruke :is() i stedet. Husk spesifisiteten til :is() er lik den mest spesifikke velgeren i velgerlisten.

Så, i stedet for å referere til en klasse vi vet (eller håper!) eksisterer med :where(), som i eksemplet ovenfor, kan vi referere til en sammensatt klasse og tag.

/* ✅ specificity score: 0,1,0 */
:is(.dummy-class, body) :where(#widget) {
  /* etc. */
}

Det alltid tilstedeværende body vil hjelpe oss å velge vår #widget element, og tilstedeværelsen av .dummy-class klasse inne i samme :is() gir body velger samme spesifisitetspoeng som en klasse (0,1,0)... og bruken av :where() sikrer at velgeren ikke blir mer spesifikk enn det.

Det er det!

Det er slik vi kan utnytte de moderne spesifisitetsadministrerende funksjonene til :is() og :where() pseudo-klasser sammen med spesifisitetskollisjonsforebyggingen som vi får når vi skriver CSS i et BEM-format. Og i en ikke så fjern fremtid, gang :has() får Firefox-støtte (det støttes for øyeblikket bak et flagg i skrivende stund) vi vil sannsynligvis pare det med :where() for å oppheve spesifisiteten.

Enten du går all-in på BEM-navngivning eller ikke, håper jeg vi kan bli enige om at det er en god ting å ha konsistens i selektorspesifisitet!

Tidstempel:

Mer fra CSS triks