Taming the Cascade With BEM and Modern CSS Selectors PlatoBlockchain Data Intelligence. Vertical Search. Ai.

Îmblanzirea cascadei cu BEM și selectoare CSS moderne

BEM. La fel ca toate tehnicile din lumea dezvoltării front-end, scrierea CSS într-un format BEM poate fi polarizant. Dar este – cel puțin în bula mea Twitter – una dintre metodologiile CSS mai apreciate.

Personal, cred că BEM este bun și cred că ar trebui să-l folosești. Dar înțeleg și de ce s-ar putea să nu.

Indiferent de părerea dumneavoastră despre BEM, acesta oferă mai multe beneficii, cel mai mare fiind că ajută la evitarea ciocnirilor de specificitate în Cascada CSS. Acest lucru se datorează faptului că, dacă sunt utilizate corect, orice selectoare scrise într-un format BEM ar trebui să aibă același scor de specificitate (0,1,0). Am proiectat CSS pentru o mulțime de site-uri web la scară largă de-a lungul anilor (gândiți-vă la guvern, universități și bănci) și am descoperit că BEM strălucește cu adevărat în aceste proiecte mai mari. Scrierea CSS este mult mai distractiv atunci când aveți încredere că stilurile pe care le scrieți sau editați nu afectează o altă parte a site-ului.

Există, de fapt, excepții în care se consideră total acceptabil să se adauge specificitate. De exemplu: cel :hover și :focus pseudo clase. Acestea au un scor de specificitate de 0,2,0. Un altul este pseudo elemente - cum ar fi ::before și ::after — care au un scor de specificitate de 0,1,1. Totuși, pentru restul acestui articol, să presupunem că nu vrem nicio altă trăsătură de specificitate. 🤓

Dar nu sunt aici să vă vând pe BEM. În schimb, vreau să vorbesc despre cum îl putem folosi alături de selectoarele CSS moderne - gândiți-vă :is(), :has(), :where(), etc. — a câștiga chiar mai mult control asupra cascada.

Ce este asta despre selectoarele CSS moderne?

Specificații CSS Selectoare Nivel 4 ne oferă câteva modalități noi (ish) puternice de a selecta elemente. Unele dintre preferatele mele includ :is(), :where(), și :not(), fiecare dintre acestea fiind acceptat de toate browserele moderne și este sigur de utilizat în aproape orice proiect în prezent.

:is() și :where() sunt practic același lucru, cu excepția modului în care influențează specificitatea. Specific, :where() are întotdeauna un scor de specificitate de 0,0,0. Da, chiar :where(button#widget.some-class) nu are specificitate. Între timp, specificul de :is() este elementul din lista sa de argumente cu cea mai mare specificitate. Deci, avem deja o distincție în cascadă între două selectoare moderne cu care putem lucra.

Incredibil de puternic :has() pseudoclasa relațională este de asemenea câștigând rapid suport pentru browser (și este cea mai mare caracteristică nouă a CSS de atunci Grilă, după umila mea părere). Cu toate acestea, la momentul scrierii, suportul pentru browser pentru :has() nu este încă suficient de bun pentru a fi utilizat în producție.

Lasă-mă să bag una dintre acele pseudo-clase în BEM-ul meu și...

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

Hopa! Vezi acel scor de specificitate? Amintiți-vă, cu BEM, în mod ideal, dorim ca selectorii noștri să aibă toți un scor de specificitate 0,1,0. De ce este 0,2,0 rău? Luați în considerare același exemplu, extins:

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

Chiar dacă al doilea selector este ultimul în ordinea sursei, specificitatea mai mare a primului selector (0,2,0) câștigă, iar culoarea lui .something--special elementele vor fi setate la red. Adică, presupunând că BEM-ul dvs. este scris corect și că elementul selectat are atât .something clasa de bază şi .something--special clasa modificatoare aplicată acesteia în HTML.

Folosite cu neglijență, aceste pseudo-clase pot afecta Cascada în moduri neașteptate. Și acest tip de inconsecvențe pot crea dureri de cap în continuare, în special pe baze de cod mai mari și mai complexe.

Dang. Si acum ce?

Amintește-ți despre ce spuneam :where() si faptul ca specificitatea lui este zero? Putem folosi asta în avantajul nostru:

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

Prima parte a acestui selector (.something) primește scorul de specificitate obișnuit de 0,1,0. Dar :where() — și tot ce este în interiorul său — are o specificitate de 0, ceea ce nu mărește și mai mult specificitatea selectorului.

:where() ne permite să cuibărăm

Oamenii cărora nu le pasă la fel de mult ca mine de specificitate (și probabil că este o mulțime de oameni, ca să fiu corect) au avut-o destul de bine când vine vorba de cuibărit. Cu câteva mișcări de tastatură fără griji, s-ar putea să ajungem cu CSS astfel (rețineți că folosesc Sass pentru concizie):

.card { ... }

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

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

În acest exemplu, avem un .card componentă. Când este un card „focalizat” (folosind cardul .card--featured clasa), titlul cardului și imaginea trebuie să fie stilate diferit. Dar, ca si noi acum Știți, codul de mai sus are ca rezultat un scor de specificitate care este inconsecvent cu restul sistemului nostru.

Un tocilar cu specificul diedic ar fi putut face asta în schimb:

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

Nu e chiar așa de rău, nu? Sincer, acesta este frumos CSS.

Există totuși un dezavantaj în HTML. Autorii BEM experimentați sunt probabil dureros de conștienți de logica șablonului neplăcută care este necesară pentru aplicarea condiționată a claselor modificatoare la mai multe elemente. În acest exemplu, șablonul HTML trebuie să adauge condiționat --featured clasa modificatoare la trei elemente (.card, .card__title, și .card__img) deși probabil chiar mai mult într-un exemplu din lumea reală. Asta e o mulțime de if declarații.

:where() selectorul ne poate ajuta să scriem mult mai puțină logică de șablon - și mai puține clase BEM de pornit - fără a adăuga la nivelul de specificitate.

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

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

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

Iată același lucru, dar în Sass (notați următorul ampersand):

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

Dacă ar trebui sau nu să optați pentru această abordare față de aplicarea claselor modificatoare la diferitele elemente copil este o chestiune de preferință personală. Dar cel puțin :where() ne dă de ales acum!

Dar HTML non-BEM?

Nu trăim într-o lume perfectă. Uneori trebuie să te ocupi de HTML care este în afara controlului tău. De exemplu, un script terță parte care injectează HTML pe care trebuie să-l stilizați. Acest marcaj de multe ori nu este scris cu numele claselor BEM. În unele cazuri, aceste stiluri nu folosesc deloc clase, ci ID-uri!

Încă o dată, :where() ne are spatele. Această soluție este ușor hackeră, deoarece trebuie să facem referință la clasa unui element undeva mai sus în arborele DOM despre care știm că există.

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

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

Totuși, referirea la un element părinte se simte puțin riscantă și restrictivă. Ce se întâmplă dacă acea clasă de părinte se schimbă sau nu există dintr-un motiv oarecare? O soluție mai bună (dar poate la fel de hackeră) ar fi folosirea :is() in schimb. Amintiți-vă, specificul :is() este egal cu cel mai specific selector din lista sa de selectoare.

Deci, în loc să facem referință la o clasă cu care știm (sau sperăm!) că există :where(), ca și în exemplul de mai sus, am putea face referire la o clasă inventată și la etichetă.

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

Cel mereu prezent body ne va ajuta să ne alegem #widget element și prezența lui .dummy-class clasa in interiorul aceluiasi :is() pronunță body selectați același scor de specificitate ca o clasă (0,1,0)... și utilizarea :where() se asigură că selectorul nu devine mai specific decât atât.

Asta e!

Acesta este modul în care putem valorifica caracteristicile moderne de gestionare a specificității :is() și :where() pseudo-clase alături de prevenirea coliziunilor de specificitate pe care o obținem când scriem CSS într-un format BEM. Și în viitorul nu prea îndepărtat, dată :has() câștigă suport pentru Firefox (în prezent este acceptat în spatele unui steag în momentul scrierii) probabil că vom dori să-l asociazăm cu :where() pentru a-i anula specificul.

Indiferent dacă mergeți all-in cu privire la denumirea BEM sau nu, sper că putem fi de acord că a avea consecvență în specificul selectorului este un lucru bun!

Timestamp-ul:

Mai mult de la CSS Trucuri