Tæmme kaskaden med BEM og moderne CSS-vælgere PlatoBlockchain Data Intelligence. Lodret søgning. Ai.

Tæmme kaskaden med BEM og moderne CSS-vælgere

BEM. Som tilsyneladende alle teknikker i verden af ​​front-end udvikling, skrive CSS i et BEM-format kan være polariserende. Men det er – i hvert fald i min Twitter-boble – en af ​​de mere vellidte CSS-metoder.

Personligt synes jeg BEM er godt, og det synes jeg du skal bruge. Men jeg forstår også, hvorfor du måske ikke.

Uanset din mening om BEM, tilbyder det flere fordele, hvoraf den største er, at det hjælper med at undgå specificitetssammenstød i CSS Cascade. Det er fordi, hvis de bruges korrekt, bør alle selektorer skrevet i et BEM-format have samme specificitetsscore (0,1,0). Jeg har bygget CSS til masser af store websteder gennem årene (tænk på regering, universiteter og banker), og det er på disse større projekter, hvor jeg har fundet ud af, at BEM virkelig skinner. At skrive CSS er meget sjovere, når du har tillid til, at de stilarter, du skriver eller redigerer, ikke påvirker en anden del af webstedet.

Der er faktisk undtagelser, hvor det anses for helt acceptabelt at tilføje specificitet. For eksempel: :hover , :focus pseudo klasser. De har en specificitetsscore på 0,2,0. En anden er pseudo-elementer - som ::before , ::after — som har en specificitetsscore på 0,1,1. For resten af ​​denne artikel, lad os dog antage, at vi ikke ønsker noget andet specificitetskryb. 🤓

Men jeg er ikke rigtig her for at sælge dig på BEM. I stedet vil jeg tale om, hvordan vi kan bruge det sammen med moderne CSS-vælgere - tænk :is(), :has(), :where(), etc. — to gain even mere kontrol af kaskaden.

Hvad er det her med moderne CSS-vælgere?

CSS Selectors Level 4 spec giver os nogle kraftfulde nye (ish) måder at vælge elementer på. Nogle af mine favoritter inkluderer :is(), :where()og :not(), som hver især understøttes af alle moderne browsere og er sikre at bruge på næsten ethvert projekt i dag.

:is() , :where() er dybest set de samme ting, bortset fra hvordan de påvirker specificiteten. Specifikt, :where() altid har en specificitetsscore på 0,0,0. Ja, endda :where(button#widget.some-class) har ingen specificitet. I mellemtiden er specificiteten af :is() er det element i sin argumentliste med den højeste specificitet. Så allerede nu har vi en kaskade-stridende skelnen mellem to moderne vælgere, som vi kan arbejde med.

Den utrolig kraftfulde :has() relationel pseudo-klasse er også hurtigt vinder browserunderstøttelse (og er den største nye funktion i CSS siden Grid, efter min ydmyge mening). Men i skrivende stund er browserunderstøttelse til :has() er ikke helt god nok til brug i produktionen endnu.

Lad mig holde en af ​​de pseudo-klasser i min BEM og...

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

Hov! Kan du se den specificitetsscore? Husk, med BEM ønsker vi ideelt set, at vores vælgere alle har en specificitetsscore på 0,1,0. Hvorfor er 0,2,0 dårligt? Overvej det samme eksempel, udvidet:

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

Selvom den anden vælger er sidst i kilderækkefølgen, er den første vælgers højere specificitet (0,2,0) vinder, og farven på .something--special elementer vil blive sat til red. Det vil sige, forudsat at din BEM er skrevet korrekt og det valgte element har både .something basisklasse og .something--special modifikatorklasse anvendt på den i HTML.

Brugt skødesløst kan disse pseudo-klasser påvirke Cascade på uventede måder. Og det er den slags uoverensstemmelser, der kan skabe hovedpine nedad, især på større og mere komplekse kodebaser.

Dang. Så hvad nu?

Husk hvad jeg sagde om :where() og det faktum, at dens specificitet er nul? Det kan vi bruge til vores fordel:

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

Den første del af denne vælger (.something) får sin sædvanlige specificitetsscore på 0,1,0. Men :where() - og alt indeni det - har en specificitet af 0, hvilket ikke øger specificiteten af ​​vælgeren yderligere.

:where() giver os mulighed for at rede

Folk, der ikke bekymrer sig så meget om specificitet som mig (og det er nok mange mennesker, for at være retfærdig), har haft det ret godt, når det kommer til rede. Med nogle ubekymrede tastaturtryk kan vi ende med CSS som denne (bemærk, at jeg bruger Sass for kortheds skyld):

.card { ... }

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

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

I dette eksempel har vi en .card komponent. Når det er et "udvalgt" kort (ved hjælp af .card--featured klasse), skal kortets titel og billede styles anderledes. Men som vi nu ved, koden ovenfor resulterer i en specificitetsscore, der ikke er i overensstemmelse med resten af ​​vores system.

En hårdfør specificitetsnørd kunne have gjort dette i stedet:

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

Det er ikke så slemt, vel? Helt ærligt, dette er smuk CSS.

Der er dog en ulempe ved HTML. Erfarne BEM-forfattere er sandsynligvis smerteligt opmærksomme på den klodsede skabelonlogik, der kræves for at betinget anvende modifikatorklasser på flere elementer. I dette eksempel skal HTML-skabelonen betinget tilføje --featured modifikatorklasse til tre elementer (.card, .card__titleog .card__img) dog sandsynligvis endnu mere i et eksempel fra den virkelige verden. Det er der mange af if udsagn.

:where() selector kan hjælpe os med at skrive meget mindre skabelonlogik - og færre BEM-klasser at starte op - uden at øge specificitetsniveauet.

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

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

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

Her er det samme, men i Sass (bemærk den efterfølgende og-tegn):

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

Hvorvidt du skal vælge denne tilgang frem for at anvende modifikatorklasser på de forskellige underordnede elementer er et spørgsmål om personlig præference. Men i det mindste :where() giver os valget nu!

Hvad med ikke-BEM HTML?

Vi lever ikke i en perfekt verden. Nogle gange har du brug for at håndtere HTML, som er uden for din kontrol. For eksempel et tredjepartsscript, der injicerer HTML, som du skal style. Den markup er ofte ikke skrevet med BEM-klassenavne. I nogle tilfælde bruger disse stilarter slet ikke klasser, men ID'er!

Endnu engang, :where() har vores ryg. Denne løsning er lidt hacky, da vi skal henvise til klassen af ​​et element et sted længere oppe i DOM-træet, som vi ved eksisterer.

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

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

Det føles dog lidt risikabelt og restriktivt at henvise til et overordnet element. Hvad hvis den forældreklasse ændrer sig eller ikke er der af en eller anden grund? En bedre (men måske lige så hacky) løsning ville være at bruge :is() i stedet. Husk specificiteten af :is() er lig med den mest specifikke vælger i dens vælgerliste.

Så i stedet for at henvise til en klasse, vi ved (eller håber!) eksisterer med :where(), som i ovenstående eksempel, kunne vi referere til en opbygget klasse og tag.

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

Den altid tilstedeværende body vil hjælpe os med at vælge vores #widget element, og tilstedeværelsen af .dummy-class klasse inde i samme :is() giver body vælger den samme specificitetsscore som en klasse (0,1,0)... og brugen af :where() sikrer, at vælgeren ikke bliver mere specifik end det.

Det er det!

Det er sådan, vi kan udnytte de moderne specificitetsstyrende funktioner i :is() , :where() pseudo-klasser sammen med den specificitetskollisionsforebyggelse, som vi får, når vi skriver CSS i et BEM-format. Og i en ikke alt for fjern fremtid, engang :has() får Firefox-understøttelse (det er i øjeblikket understøttet bag et flag i skrivende stund) vil vi sandsynligvis gerne parre det med :where() for at fortryde dets specificitet.

Uanset om du går all-in på BEM-navngivning eller ej, håber jeg, vi kan blive enige om, at det er en god ting at have konsistens i selektorspecificitet!

Tidsstempel:

Mere fra CSS-tricks