Die Kaskade mit BEM und modernen CSS-Selektoren zähmen PlatoBlockchain Data Intelligence. Vertikale Suche. Ai.

Zähmung der Kaskade mit BEM und modernen CSS-Selektoren

BEM. Wie scheinbar alle Techniken in der Welt der Front-End-Entwicklung, Schreiben von CSS in einem BEM-Format kann polarisieren. Aber es ist – zumindest in meiner Twitter-Blase – eine der beliebtesten CSS-Methoden.

Ich persönlich finde BEM gut, und ich denke, Sie sollten es verwenden. Aber ich verstehe auch, warum Sie vielleicht nicht.

Unabhängig von Ihrer Meinung zu BEM bietet es mehrere Vorteile, von denen der größte darin besteht, dass es hilft, Spezifitätskonflikte in der CSS-Kaskade zu vermeiden. Das liegt daran, dass bei richtiger Verwendung alle in einem BEM-Format geschriebenen Selektoren denselben Spezifitätswert haben sollten (0,1,0). Ich habe das CSS im Laufe der Jahre für viele große Websites entwickelt (denken Sie an Regierungen, Universitäten und Banken), und bei diesen größeren Projekten habe ich festgestellt, dass BEM wirklich glänzt. Das Schreiben von CSS macht viel mehr Spaß, wenn Sie darauf vertrauen können, dass die Stile, die Sie schreiben oder bearbeiten, keinen anderen Teil der Website beeinflussen.

Es gibt tatsächlich Ausnahmen, bei denen es als völlig akzeptabel erachtet wird, Spezifizierungen hinzuzufügen. Zum Beispiel: die :hover und :focus Pseudoklassen. Diese haben einen Spezifitätswert von 0,2,0. Ein anderes sind Pseudoelemente – wie ::before und ::after — die einen Spezifitätswert von haben 0,1,1. Gehen wir für den Rest dieses Artikels davon aus, dass wir keine weiteren Spezifitäts-Creeps wollen. 🤓

Aber ich bin nicht wirklich hier, um Ihnen BEM zu verkaufen. Stattdessen möchte ich darüber sprechen, wie wir es zusammen mit modernen CSS-Selektoren verwenden können – denken Sie :is(), :has(), :where(), etc. - um sogar zu gewinnen mehr Kontrolle die Kaskade.

Was hat es mit modernen CSS-Selektoren auf sich?

Das CSS-Selektoren Level 4 Spezifikation gibt uns einige leistungsstarke neue (ähnliche) Möglichkeiten, Elemente auszuwählen. Einige meiner Favoriten sind :is(), :where() und :not(), die von allen modernen Browsern unterstützt werden und heutzutage für fast jedes Projekt sicher verwendet werden können.

:is() und :where() sind im Grunde dasselbe, außer wie sie die Spezifität beeinflussen. Speziell, :where() hat immer einen Spezifitätswert von 0,0,0. Ja, sogar :where(button#widget.some-class) hat keine Besonderheit. Inzwischen ist die Besonderheit von :is() ist das Element in seiner Argumentliste mit der höchsten Spezifität. Wir haben also bereits eine Unterscheidung zwischen zwei modernen Selektoren, mit der wir arbeiten können.

Die unglaublich mächtig :has() relationale Pseudo-Klasse ist auch schnell zunehmende Browserunterstützung (und ist das größte neue Feature von CSS seitdem Gitter, meiner bescheidenen Meinung nach). Zum Zeitpunkt des Schreibens wurde jedoch die Browserunterstützung für :has() noch nicht ganz gut genug für den Einsatz in der Produktion.

Lass mich eine dieser Pseudo-Klassen in mein BEM stecken und …

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

Hoppla! Sehen Sie diesen Spezifitätswert? Denken Sie daran, dass wir bei BEM idealerweise möchten, dass unsere Selektoren alle einen Spezifitätswert von haben 0,1,0. Warum ist 0,2,0 Schlecht? Betrachten Sie dasselbe Beispiel, erweitert:

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

Obwohl der zweite Selektor in der Quellenreihenfolge an letzter Stelle steht, ist die höhere Spezifität des ersten Selektors (0,2,0) gewinnt, und die Farbe von .something--special Elemente werden gesetzt red. Das heißt, vorausgesetzt, Ihr BEM ist richtig geschrieben und das ausgewählte Element hat beides .something Basisklasse und .something--special Modifikatorklasse, die im HTML darauf angewendet wird.

Unvorsichtig verwendet, können diese Pseudo-Klassen die Kaskade auf unerwartete Weise beeinflussen. Und es sind diese Arten von Inkonsistenzen, die später Kopfschmerzen verursachen können, insbesondere bei größeren und komplexeren Codebasen.

Verdammt. Was nun?

Denken Sie daran, was ich gesagt habe :where() und die Tatsache, dass seine Spezifität null ist? Das können wir zu unserem Vorteil nutzen:

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

Der erste Teil dieses Selektors (.something) erhält seine übliche Spezifitätsbewertung von 0,1,0. Aber :where() – und alles darin – hat eine Besonderheit von 0, was die Spezifität des Selektors nicht weiter erhöht.

:where() lässt uns nisten

Leute, die sich nicht so sehr um Spezifität kümmern wie ich (und das sind wahrscheinlich viele Leute, um fair zu sein), hatten es ziemlich gut, wenn es um das Verschachteln ging. Mit einigen sorglosen Tastatureingaben könnten wir mit CSS wie diesem enden (beachten Sie, dass ich Sass der Kürze halber verwende):

.card { ... }

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

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

In diesem Beispiel haben wir eine .card Komponente. Wenn es sich um eine „empfohlene“ Karte handelt (mit der .card--featured Klasse), müssen der Titel und das Bild der Karte anders gestaltet werden. Aber wie wir jetzt an Wissen Sie, der obige Code führt zu einem Spezifitätswert, der nicht mit dem Rest unseres Systems übereinstimmt.

Ein eingefleischter Spezifitäts-Nerd hätte stattdessen vielleicht Folgendes getan:

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

Das ist nicht so schlimm, oder? Ehrlich gesagt, das ist wunderschönes CSS.

Es gibt jedoch einen Nachteil im HTML. Erfahrene BEM-Autoren sind sich wahrscheinlich der klobigen Vorlagenlogik bewusst, die erforderlich ist, um Modifikatorklassen bedingt auf mehrere Elemente anzuwenden. In diesem Beispiel muss die HTML-Vorlage die bedingt hinzufügen --featured Modifikatorklasse auf drei Elemente (.card, .card__title und .card__img), obwohl wahrscheinlich noch mehr in einem realen Beispiel. Das ist eine Menge if Aussagen.

Das :where() selector kann uns helfen, viel weniger Template-Logik – und weniger BEM-Klassen zum Booten – zu schreiben, ohne die Spezifität zu erhöhen.

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

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

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

Hier ist dasselbe, aber in Sass (beachten Sie die nachfolgende kaufmännisches Und):

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

Ob Sie sich für diesen Ansatz entscheiden sollten oder nicht, anstatt Modifikatorklassen auf die verschiedenen untergeordneten Elemente anzuwenden, ist eine Frage der persönlichen Präferenz. Aber wenigstens :where() gibt uns jetzt die Wahl!

Was ist mit Nicht-BEM-HTML?

Wir leben nicht in einer perfekten Welt. Manchmal müssen Sie mit HTML umgehen, das außerhalb Ihrer Kontrolle liegt. Beispielsweise ein Skript eines Drittanbieters, das HTML einfügt, das Sie formatieren müssen. Dieses Markup wird oft nicht mit BEM-Klassennamen geschrieben. In einigen Fällen verwenden diese Stile überhaupt keine Klassen, sondern IDs!

Wiederum :where() hat unseren Rücken. Diese Lösung ist etwas hackig, da wir die Klasse eines Elements irgendwo weiter oben im DOM-Baum referenzieren müssen, von dem wir wissen, dass es existiert.

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

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

Der Verweis auf ein übergeordnetes Element fühlt sich jedoch etwas riskant und einschränkend an. Was ist, wenn sich diese übergeordnete Klasse ändert oder aus irgendeinem Grund nicht vorhanden ist? Eine bessere (aber vielleicht genauso hacky) Lösung wäre zu verwenden :is() stattdessen. Denken Sie daran, die Besonderheit von :is() ist gleich dem spezifischsten Selektor in seiner Selektorliste.

Anstatt also auf eine Klasse zu verweisen, von der wir wissen (oder hoffen!), dass sie existiert :where(), wie im obigen Beispiel, könnten wir auf eine erfundene Klasse und die verweisen -Tag.

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

Das allgegenwärtige body hilft uns bei der Auswahl unserer #widget Element und das Vorhandensein des .dummy-class Klasse innerhalb der gleichen :is() gibt die body Selektor denselben Spezifitätswert wie eine Klasse (0,1,0)… und die Verwendung von :where() stellt sicher, dass der Selektor nicht spezifischer wird.

Fertig! :)

Auf diese Weise können wir die modernen Eigenschaftenverwaltungsfunktionen von nutzen :is() und :where() Pseudo-Klassen neben der Spezifitätskollisionsverhinderung, die wir erhalten, wenn wir CSS in einem BEM-Format schreiben. Und in nicht allzu ferner Zukunft einmal :has() erhält Firefox-Unterstützung (es wird derzeit hinter einem Flag zum Zeitpunkt des Schreibens unterstützt) Wir werden es wahrscheinlich mit :where() paaren wollen, um seine Spezifität rückgängig zu machen.

Unabhängig davon, ob Sie bei der BEM-Benennung All-in gehen oder nicht, ich hoffe, wir können uns darauf einigen, dass es eine gute Sache ist, eine einheitliche Selektorspezifität zu haben!

Zeitstempel:

Mehr von CSS-Tricks