Oswajanie kaskady za pomocą BEM i nowoczesnych selektorów CSS PlatoBlockchain Data Intelligence. Wyszukiwanie pionowe. AI.

Oswajanie kaskady za pomocą BEM i nowoczesnych selektorów CSS

BEM. Jak pozornie wszystkie techniki w świecie front-end developmentu, pisanie CSS w formacie BEM może polaryzować. Ale jest to – przynajmniej w mojej bańce na Twitterze – jedna z bardziej lubianych metodologii CSS.

Osobiście uważam, że BEM jest dobry i myślę, że powinieneś go używać. Ale rozumiem też, dlaczego możesz tego nie robić.

Niezależnie od Twojej opinii na temat BEM, oferuje on kilka korzyści, z których największą jest to, że pomaga uniknąć kolizji specyficzności w kaskadzie CSS. Dzieje się tak, ponieważ przy prawidłowym użyciu wszelkie selektory zapisane w formacie BEM powinny mieć ten sam wynik specyficzności (0,1,0). Przez lata tworzyłem architekturę CSS dla wielu dużych stron internetowych (myślę, że rząd, uniwersytety i banki) i właśnie w tych większych projektach odkryłem, że BEM naprawdę błyszczy. Pisanie CSS jest o wiele przyjemniejsze, gdy masz pewność, że style, które piszesz lub edytujesz, nie wpływają na inne części witryny.

W rzeczywistości istnieją wyjątki, w których uważa się, że dodanie specyficzności jest całkowicie dopuszczalne. Na przykład: :hover i :focus pseudoklasy. Te mają wynik specyficzności 0,2,0. Innym są pseudoelementy — np ::before i ::after — które mają wynik specyficzności równy 0,1,1. Jednak przez resztę tego artykułu załóżmy, że nie chcemy żadnego innego pełzania specyficzności. 🤓

Ale tak naprawdę nie jestem tu po to, by sprzedawać cię na BEM. Zamiast tego chcę porozmawiać o tym, jak możemy go używać wraz z nowoczesnymi selektorami CSS — pomyśl :is(), :has(), :where(), itd. — wyrównać jeszcze kontrola Kaskada.

O co chodzi z nowoczesnymi selektorami CSS?

Połączenia Specyfikacja selektorów CSS poziomu 4 daje nam kilka nowych, potężnych sposobów wybierania elementów. Niektóre z moich ulubionych to :is(), :where(), :not(), z których każda jest obsługiwana przez wszystkie nowoczesne przeglądarki i jest obecnie bezpieczna w prawie każdym projekcie.

:is() i :where() są w zasadzie tym samym, z wyjątkiem tego, jak wpływają na specyficzność. Konkretnie, :where() zawsze ma punktację specyficzności 0,0,0. Tak, nawet :where(button#widget.some-class) nie ma specyfiki. Tymczasem specyfika :is() jest elementem na liście argumentów o najwyższej specyficzności. Tak więc mamy już rozróżnienie między dwoma nowoczesnymi selektorami, z którymi możemy pracować.

Niesamowicie potężny :has() pseudoklasa relacyjna też jest szybko zyskuje wsparcie dla przeglądarek (i jest największą nową funkcją CSS od tamtego czasu Krata, moim skromnym zdaniem). Jednak w chwili pisania tego tekstu obsługa przeglądarki dla :has() nie jest jeszcze wystarczająco dobry do użytku w produkcji.

Pozwól, że wsadzę jedną z tych pseudoklas do mojego BEM i…

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

Ups! Widzisz ten wynik specyficzności? Pamiętaj, że w przypadku BEM idealnie chcemy, aby nasi selektorzy mieli wynik specyficzności równy 0,1,0. Dlaczego jest 0,2,0 zły? Rozważ ten sam przykład, rozszerzony:

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

Mimo że drugi selektor jest ostatni w kolejności źródłowej, wyższa specyficzność pierwszego selektora (0,2,0) wygrywa, a kolor .something--special elementy zostaną ustawione na red. Oznacza to, że zakładając, że Twój BEM jest napisany poprawnie, a wybrany element ma oba elementy .something klasa podstawowa i .something--special klasa modyfikatora zastosowana do niej w kodzie HTML.

Używane beztrosko, te pseudoklasy mogą wpłynąć na Kaskadę w nieoczekiwany sposób. I właśnie tego rodzaju niespójności mogą przyprawiać o ból głowy, zwłaszcza w przypadku większych i bardziej złożonych baz kodu.

Cholera. I co teraz?

Pamiętaj o czym mówiłem :where() i fakt, że jego specyficzność jest zerowa? Możemy to wykorzystać na naszą korzyść:

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

Pierwsza część tego selektora (.something) uzyskuje swój zwykły wynik specyficzności równy 0,1,0, Ale :where() — i wszystko w nim — ma specyfikę 0, co nie zwiększa dalej specyficzności selektora.

:where() pozwala nam gniazdować

Ludzie, którzy nie dbają tak bardzo jak ja o specyfikę (a jest to prawdopodobnie wielu ludzi, szczerze mówiąc) mieli całkiem nieźle, jeśli chodzi o zagnieżdżanie. Po kilku beztroskich naciśnięciach klawiatury możemy skończyć z CSS w następujący sposób (pamiętaj, że używam Sass dla zwięzłości):

.card { ... }

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

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

W tym przykładzie mamy a .card składnik. Gdy jest to karta „polecana” (przy użyciu rozszerzenia .card--featured class), tytuł i obraz karty muszą mieć inny styl. Ale jak my już dziś wiesz, powyższy kod daje wynik specyficzności, który jest niespójny z resztą naszego systemu.

Zagorzały kujon specyficzności mógłby to zrobić zamiast tego:

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

To nie jest takie złe, prawda? Szczerze mówiąc, to jest piękny CSS.

HTML ma jednak wadę. Doświadczeni autorzy BEM są prawdopodobnie boleśnie świadomi nieporęcznej logiki szablonów, która jest wymagana do warunkowego zastosowania klas modyfikatorów do wielu elementów. W tym przykładzie szablon HTML musi warunkowo dodać plik --featured klasa modyfikatora do trzech elementów (.card, .card__title, .card__img), choć prawdopodobnie nawet więcej w rzeczywistym przykładzie. To dużo if sprawozdania.

Połączenia :where() selektor może pomóc nam napisać o wiele mniej logiki szablonów — i mniej klas BEM do uruchomienia — bez zwiększania poziomu szczegółowości.

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

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

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

Oto to samo, ale w Sass (zwróć uwagę na końcowy ampersandy):

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

To, czy powinieneś wybrać to podejście zamiast stosowania klas modyfikujących do różnych elementów potomnych, jest kwestią osobistych preferencji. Ale przynajmniej :where() daje nam wybór teraz!

A co z kodem HTML innym niż BEM?

Nie żyjemy w idealnym świecie. Czasami musisz poradzić sobie z kodem HTML, na który nie masz wpływu. Na przykład skrypt innej firmy, który wstrzykuje kod HTML, który trzeba stylizować. Ten znacznik często nie jest zapisywany z nazwami klas BEM. W niektórych przypadkach te style w ogóle nie używają klas, ale identyfikatory!

Po raz :where() ma nasze plecy. To rozwiązanie jest nieco hackerskie, ponieważ musimy odwołać się do klasy elementu gdzieś wyżej w drzewie DOM, o którym wiemy, że istnieje.

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

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

Odwoływanie się do elementu nadrzędnego wydaje się jednak trochę ryzykowne i ograniczające. Co się stanie, jeśli ta klasa nadrzędna zmieni się lub nie będzie jej z jakiegoś powodu? Lepszym (ale być może równie hackowym) rozwiązaniem byłoby użycie :is() zamiast. Pamiętaj, specyfika :is() jest równy najbardziej szczegółowemu selektorowi na jego liście selektorów.

Zamiast więc odwoływać się do klasy, o której wiemy (lub mamy nadzieję!), że istnieje :where(), jak w powyższym przykładzie, moglibyśmy odwołać się do utworzonej klasy i etykietka.

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

Wszechobecny body pomoże nam wybrać nasz #widget pierwiastek i obecność tzw .dummy-class klasa wewnątrz tego samego :is() daje body selektor ma taki sam wynik specyficzności jak klasa (0,1,0)… i wykorzystanie :where() zapewnia, że ​​selektor nie będzie bardziej szczegółowy.

To jest to!

W ten sposób możemy wykorzystać nowoczesne funkcje zarządzania specyficznością :is() i :where() pseudoklasy wraz ze specyficznością zapobiegania kolizjom, którą uzyskujemy pisząc CSS w formacie BEM. A w niezbyt odległej przyszłości, pewnego razu :has() zyskuje wsparcie dla Firefoksa (jest obecnie obsługiwany za flagą w momencie pisania) prawdopodobnie będziemy chcieli sparować go z :where() , aby cofnąć jego specyfikę.

Niezależnie od tego, czy zdecydujesz się na nazewnictwo BEM, czy nie, mam nadzieję, że zgodzimy się, że spójność w specyfice selektora jest dobrą rzeczą!

Znak czasu:

Więcej z Sztuczki CSS