BEM. Mint látszólag minden technika a front-end fejlesztés világában, CSS írása BEM formátumban polarizáló lehet. De ez – legalábbis az én Twitter-buborékomban – az egyik jobban kedvelt CSS-módszer.
Személy szerint szerintem a BEM jó, és szerintem használd. De azt is értem, hogy miért nem.
Függetlenül a BEM-mel kapcsolatos véleményétől, számos előnnyel jár, a legnagyobb az, hogy segít elkerülni a specifikusság ütközéseket a CSS Cascade-ban. Ennek az az oka, hogy megfelelő használat esetén minden BEM formátumban írt szelektornak azonos specifikussági pontszámmal kell rendelkeznie (0,1,0
). Az évek során rengeteg nagyméretű webhelyhez terveztem a CSS-t (gondoljunk csak a kormányra, az egyetemekre és a bankokra), és ezeken a nagyobb projekteken tapasztaltam, hogy a BEM igazán ragyogó. A CSS írása sokkal szórakoztatóbb, ha biztos abban, hogy az írott vagy szerkesztett stílusok nincsenek hatással a webhely más részére.
Valójában vannak kivételek, ahol teljesen elfogadhatónak tekinthető a specifikusság hozzáadása. Például: a :hover
és a :focus
pszeudo osztályok. Ezeknek specifitási pontszámuk van 0,2,0
. A másik a pszeudo elemek – mint pl ::before
és a ::after
— amelyek specifitási pontszáma: 0,1,1
. A cikk hátralévő részében azonban tegyük fel, hogy nem akarunk semmilyen más specifikusságot. 🤓
De valójában nem azért vagyok itt, hogy eladjak téged a BEM-en. Ehelyett arról szeretnék beszélni, hogyan használhatjuk a modern CSS-szelektorok mellett – gondoljunk csak bele :is()
, :has()
, :where()
stb. – még nyerni több ellenőrzése a Cascade.
Mi ez a modern CSS-választókkal?
A CSS Selectors Level 4 spec néhány hatékony új módszert kínál az elemek kiválasztására. Néhány kedvencem közé tartozik :is()
, :where()
és :not()
, amelyek mindegyikét minden modern böngésző támogatja, és manapság szinte minden projekthez biztonságosan használható.
:is()
és a :where()
alapvetően ugyanazok, kivéve azt, hogy hogyan befolyásolják a specifikusságot. Kimondottan, :where()
mindig rendelkezik egy specifikussági pontszámmal 0,0,0
. Igen, akár :where(button#widget.some-class)
nincs sajátossága. Eközben a sajátossága :is()
az argumentumlistájában a legnagyobb specifitással rendelkező elem. Tehát már van egy lépcsőzetes különbségtétel két modern szelektor között, amelyekkel dolgozhatunk.
A hihetetlenül erős :has()
relációs pszeudoosztály is az gyorsan növekvő böngésző támogatás (és azóta a CSS legnagyobb új funkciója Rács, szerény véleményem szerint). Az írás idején azonban a böngésző támogatja a :has()
még nem elég jó a termelésben való használatra.
Ragasszunk be egy ilyen pszeudoosztályt a BEM-embe, és…
/* ❌ specificity score: 0,2,0 */
.something:not(.something--special) {
/* styles for all somethings, except for the special somethings */
}
Hoppá! Látod ezt a specifikussági pontszámot? Ne feledje, hogy a BEM esetében ideális esetben azt szeretnénk, ha választóink mindegyikének specifikussági pontszáma lenne 0,1,0
. Miért van 0,2,0
rossz? Tekintsük ugyanezt a példát kibontva:
.something:not(.something--special) {
color: red;
}
.something--special {
color: blue;
}
Annak ellenére, hogy a második szelektor a forrás sorrendjében az utolsó, az első szelektor magasabb specifitása (0,2,0
) nyer, és a színe .something--special
elemre lesznek beállítva red
. Ez azt jelenti, hogy feltételezzük, hogy a BEM megfelelően van megírva, és a kiválasztott elem mindkét elemet tartalmazza .something
alaposztály és .something--special
módosító osztályt alkalmaznak rá a HTML-ben.
Gondatlanul használva ezek az álosztályok váratlan módon befolyásolhatják a kaszkádot. És pont az ilyen jellegű következetlenségek okozhatnak fejfájást a soron, különösen a nagyobb és összetettebb kódbázisokon.
Dang. Akkor most mi legyen?
Emlékezz, miről beszéltem :where()
és az, hogy a specifitása nulla? Ezt az előnyünkre fordíthatjuk:
/* ✅ specificity score: 0,1,0 */
.something:where(:not(.something--special)) {
/* etc. */
}
Ennek a választónak az első része (.something
) megkapja szokásos specifitási pontszámát 0,1,0
. De :where()
– és mindennek, ami benne van – megvan a sajátossága 0
, ami nem növeli tovább a szelektor specifitását.
:where()
lehetővé teszi számunkra, hogy fészkelődjünk
Azok az emberek, akiket nem érdekel annyira a konkrétság, mint én (és az igazat megvallva valószínűleg sok emberről van szó), nagyon jól jártak a fészekrakás terén. Néhány gondtalan billentyűzetnyomással a CSS-t így zárhatjuk le (megjegyzendő, hogy a Sass-t a rövidség kedvéért használom):
.card { ... }
.card--featured {
/* etc. */
.card__title { ... }
.card__title { ... }
}
.card__title { ... }
.card__img { ... }
Ebben a példában van egy .card
összetevő. Ha ez egy „kiemelt” kártya (a .card--featured
osztály), a kártya címét és képét más stílusban kell kialakítani. De ahogy mi is Most Tudja, a fenti kód olyan specifikussági pontszámot eredményez, amely nem egyeztethető össze rendszerünk többi részével.
Lehet, hogy egy megrögzött specifikus nerd ezt tette volna helyette:
.card { ... }
.card--featured { ... }
.card__title { ... }
.card__title--featured { ... }
.card__img { ... }
.card__img--featured { ... }
Ez nem is olyan rossz, igaz? Őszintén szólva, ez egy gyönyörű CSS.
A HTML-nek azonban van egy hátránya. A tapasztalt BEM szerzők valószínűleg fájdalmasan tudatában vannak annak a makacs sablonlogikának, amely szükséges ahhoz, hogy feltételesen alkalmazzák a módosító osztályokat több elemre. Ebben a példában a HTML-sablonnak feltételesen hozzá kell adnia a --featured
módosító osztály három elemre (.card
, .card__title
és .card__img
), bár valós példában valószínűleg még inkább. Ez sok if
nyilatkozatokat.
A :where()
A kiválasztó segítségével sokkal kevesebb sablonlogikát írhatunk – és kevesebb indítandó BEM-osztályt – anélkül, hogy növelnénk a specifikusság szintjét.
.card { ... }
.card--featured { ... }
.card__title { ... }
:where(.card--featured) .card__title { ... }
.card__img { ... }
:where(.card--featured) .card__img { ... }
Itt ugyanaz a dolog, csak Sassban (figyeld meg a végét és jelek):
.card { ... }
.card--featured { ... }
.card__title {
/* etc. */
:where(.card--featured) & { ... }
}
.card__img {
/* etc. */
:where(.card--featured) & { ... }
}
Az, hogy ezt a megközelítést választja-e a módosító osztályok alkalmazása helyett a különböző gyermekelemekre, személyes preferencia kérdése. De legalább :where()
most választási lehetőséget ad nekünk!
Mi a helyzet a nem BEM HTML-lel?
Nem élünk tökéletes világban. Néha olyan HTML-kóddal kell megküzdenie, amely kívül esik az ellenőrzésén. Például egy harmadik féltől származó szkript, amely beilleszti a stílushoz szükséges HTML-kódot. Ezt a jelölést gyakran nem BEM-osztálynevekkel írják. Egyes esetekben ezek a stílusok egyáltalán nem osztályokat használnak, hanem azonosítókat!
Még egyszer, :where()
hátunk van. Ez a megoldás kissé hibás, mivel egy olyan elem osztályára kell hivatkoznunk, amely valahol a DOM-fában feljebb van, és amelyről tudjuk, hogy létezik.
/* ❌ specificity score: 1,0,0 */
#widget {
/* etc. */
}
/* ✅ specificity score: 0,1,0 */
.page-wrapper :where(#widget) {
/* etc. */
}
A szülőelemre való hivatkozás azonban kissé kockázatosnak és korlátozónak tűnik. Mi van, ha a szülői osztály megváltozik, vagy valamilyen oknál fogva nincs? Egy jobb (de talán ugyanolyan durva) megoldás lenne a használata :is()
helyette. Ne feledje, a sajátossága :is()
egyenlő a választólistájában szereplő legspecifikusabb szelektorral.
Tehát ahelyett, hogy egy olyan osztályra hivatkoznánk, amelyről tudjuk (vagy reméljük!) létezik :where()
, mint a fenti példában, hivatkozhatunk egy összeállított osztályra és a címke.
/* ✅ specificity score: 0,1,0 */
:is(.dummy-class, body) :where(#widget) {
/* etc. */
}
A mindenkori jelen body
segít kiválasztani #widget
elem, és a jelenléte .dummy-class
osztályon belül ugyanaz :is()
adja a body
válassza ki ugyanazt a specifitási pontszámot, mint egy osztály (0,1,0
)… és használata :where()
biztosítja, hogy a választó ne legyen ennél pontosabb.
Ez az!
Így tudjuk kiaknázni a modern specifikációkezelő funkcióit :is()
és a :where()
pszeudoosztályok, valamint a specifikus ütközésmegelőzés, amelyet akkor kapunk, ha CSS-t írunk BEM formátumban. És a nem túl távoli jövőben, egyszer :has()
elnyeri a Firefox támogatását (jelenleg egy zászló mögött van támogatva az írás idején) valószínűleg párosítani szeretnénk a :where()-vel, hogy visszavonjuk sajátosságait.
Akár all-in megy a BEM-elnevezéssel, akár nem, remélem, egyetértünk abban, hogy a választóspecifikusság következetessége jó dolog!