ABEM. Eine nützlichere Anpassung von BEM.

Avatar of Daniel Tonon
Daniel Tonon am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

BEM (Block Element Modifier) ist eine beliebte Konvention zur Benennung von CSS-Klassen, die CSS wartbarer macht. Dieser Artikel setzt voraus, dass Sie mit der Namenskonvention bereits vertraut sind. Wenn nicht, können Sie mehr darüber auf getbem.com erfahren, um sich mit den Grundlagen vertraut zu machen.

Die Standard-Syntax für BEM ist

block-name__element-name--modifier-name

Ich persönlich bin ein großer Fan der Methodik hinter der Namenskonvention. Das Aufteilen Ihrer Stile in kleine Komponenten ist wesentlich einfacher zu warten, als ein Meer von hoher Spezifität, die überall in Ihrer Stylesheet verstreut ist. Allerdings gibt es ein paar Probleme mit der Syntax, die zu Problemen in der Produktion sowie zu Verwirrung bei Entwicklern führen können. Ich ziehe es vor, stattdessen eine leicht abgewandelte Version der Syntax zu verwenden. Ich nenne sie ABEM (Atomic Block Element Modifier)

[a/m/o]-blockName__elementName -modifierName

Ein Atomic Design Präfix

Das a/m/o ist ein Atomic Design Präfix. Nicht zu verwechseln mit Atomic CSS, was etwas völlig anderes ist. Atomic Design ist eine Methodik zur Organisation Ihrer Komponenten, die die Wiederverwendbarkeit von Code maximiert. Es teilt Ihre Komponenten in drei Ordner auf: Atome, Moleküle und Organismen. Atome sind super einfache Komponenten, die im Allgemeinen nur aus einem einzigen Element bestehen (z. B. eine Button-Komponente). Moleküle sind kleine Gruppen von Elementen und/oder Komponenten (z. B. ein einzelnes Formularfeld, das ein Label und ein Eingabefeld anzeigt). Organismen sind große, komplexe Komponenten, die aus vielen Molekül- und Atomkomponenten bestehen (z. B. ein vollständiges Registrierungsformular).

An Atomic Design Diagram showing atoms inside molecules inside an organism

Die Schwierigkeit, Atomic Design mit klassischem BEM zu verwenden, liegt darin, dass es keine Anzeige gibt, um welche Art von Komponente ein Block handelt. Dies kann es schwierig machen, zu wissen, wo sich der Code für diese Komponente befindet, da Sie möglicherweise in drei separaten Ordnern suchen müssen, um ihn zu finden. Das Hinzufügen des Atomic-Präfixes am Anfang macht es sofort offensichtlich, in welchem Ordner die Komponente gespeichert ist.

camelCase

Ermöglicht benutzerdefinierte Gruppierungen

Klassisches BEM trennt jedes einzelne Wort innerhalb eines Abschnitts mit einem einzelnen Bindestrich. Beachten Sie, dass das Atomic-Präfix im obigen Beispiel ebenfalls durch einen Bindestrich vom Rest des Klassennamens getrennt ist. Schauen Sie sich an, was passiert, wenn Sie BEM classic vs. camelCase ein Atomic-Präfix hinzufügen

/* classic + atomic prefix */
.o-subscribe-form__field-item {}

/* camelCase + atomic prefix */
.o-subscribeForm__fieldItem {}

Auf den ersten Blick sieht der Komponentenname bei der klassischen Methode so aus, als ob er "o subscribe form" genannt wird. Die Bedeutung des "o" geht völlig verloren. Wenn Sie jedoch die "o-" auf die camelCase-Version anwenden, ist klar, dass sie absichtlich als separates Informationsstück zum Komponentennamen geschrieben wurde.

Jetzt könnten Sie das Atomic-Präfix auf klassisches BEM anwenden, indem Sie das "o" großschreiben, wie hier

/* classic + capitalized atomic prefix */
.O-subscribe-form__field-item {}

Das würde das Problem lösen, dass das "o" im Rest des Klassennamens verloren geht. Es löst jedoch nicht das grundlegende Problem der klassischen BEM-Syntax. Durch die Trennung der Wörter mit Bindestrichen ist das Bindestrichzeichen nicht mehr für Sie als Gruppierungsmechanismus verfügbar. Durch die Verwendung von camelCase können Sie das Bindestrichzeichen für zusätzliche Gruppierungen freigeben, auch wenn diese Gruppierung nur das Hinzufügen einer Zahl am Ende eines Klassennamens ist.

Ihr Gehirn verarbeitet die Gruppierungen schneller

camelCase hat auch den zusätzlichen Vorteil, dass die Gruppierung der Klassennamen leichter mental verarbeitet werden kann. Bei camelCase stellt jede Lücke, die Sie in einem Klassennamen sehen, eine Art Gruppierung dar. Bei klassischem BEM könnte jede Lücke entweder eine Gruppierung oder ein Leerzeichen zwischen zwei Wörtern derselben Gruppe sein.

Betrachten Sie diese Silhouette einer klassischen BEM-Klasse (plus Atomic-Präfix) und versuchen Sie herauszufinden, wo die Präfix-, Block-, Element- und Modifier-Abschnitte beginnen und enden

classic BEM silhouette

Ok, versuchen Sie es jetzt mit dieser. Es ist genau dieselbe Klasse wie die obige, nur dass diesmal camelCase anstelle von Bindestrichen verwendet wird, um jedes Wort zu trennen

camel case BEM silhouette

Das war viel einfacher, oder? Diese Silhouetten sind im Wesentlichen das, was Ihr Gehirn sieht, wenn es durch Ihren Code scrollt. All die zusätzlichen Bindestriche im Klassennamen machen die Gruppierungen viel weniger klar. Wenn Sie Ihren Code lesen, versucht Ihr Gehirn zu verarbeiten, ob die aufgetretenen Lücken neue Gruppierungen oder nur neue Wörter sind. Dieser Mangel an Klarheit belastet Ihre kognitive Last während der Arbeit.

klassisches BEM + Atomic-Präfix
classic BEM silhouette revealed
camelCase BEM + Atomic-Präfix
camel case BEM silhouette revealed

Verwenden Sie Multi-Klassen-Selektoren (verantwortungsbewusst)

Eine der goldenen Regeln in BEM ist, dass jeder Selektor nur eine einzige Klasse enthalten soll. Die Idee ist, dass dies CSS wartbar hält, indem die Spezifität von Selektoren niedrig und überschaubar bleibt. Einerseits stimme ich zu, dass niedrige Spezifität besser ist als wuchernde Spezifität. Andererseits bin ich entschieden dagegen, dass eine strenge Regel von einer Klasse pro Selektor das Beste für Projekte ist. Die Verwendung einiger Multi-Klassen-Selektoren in Ihren Stilen kann die Wartbarkeit tatsächlich verbessern und nicht verschlechtern.

„Aber das führt zu höherer Spezifität! Wissen Sie nicht, dass Spezifität von Natur aus böse ist?!?

Spezifität != böse.

Unkontrollierte Spezifität, die außer Kontrolle geraten ist = böse.

Einige Deklarationen mit höherer Spezifität bedeuten nicht sofort, dass Ihr CSS schwieriger zu warten ist. Wenn sie auf die richtige Weise verwendet werden, können bestimmte Regeln mit höherer Spezifität CSS sogar einfacher zu warten machen. Der Schlüssel zum Schreiben von wartbarem CSS mit ungleicher Spezifität besteht darin, Spezifität gezielt hinzuzufügen und nicht nur, weil ein Listenelement zufällig in einem Listenelement vorkommt.

Außerdem wollen wir nicht eigentlich, dass unsere Modifier-Stile mehr Macht über Elemente haben als Standardstile? Rückwärts zu biegen, um Modifier-Stile auf der gleichen Spezifitätsebene wie normale Stile zu halten, erscheint mir albern. Wann wollen Sie eigentlich, dass Ihre regulären Standardstile Ihre speziell ausgewiesenen Modifier-Stile überschreiben?

Die Trennung des Modifiers führt zu saubererem HTML

Dies ist die größte Änderung an der Syntax, die ABEM einführt. Anstatt den Modifier mit der Element-Klasse zu verbinden, wenden Sie ihn als separate Klasse an.

Eines der Dinge, über die sich praktisch jeder beschwert, wenn er zum ersten Mal anfängt, BEM zu lernen, ist, wie hässlich es ist. Es ist besonders schlimm, wenn es um Modifier geht. Sehen Sie sich dieses Gräuel an. Es hat nur drei Modifier angewendet und sieht trotzdem wie ein Zugwrack aus

B__E–M
<button class="block-name__element-name block-name__element-name--small block-name__element-name--green block-name__element-name--active">
  Submit
</button>

Schauen Sie sich die ganze Wiederholung an! Diese Wiederholung macht es ziemlich schwierig zu lesen, was sie eigentlich zu tun versucht. Schauen Sie sich jetzt dieses ABEM-Beispiel an, das die gleichen Modifier wie im vorherigen Beispiel hat

A-B__E -M
<button class="a-blockName__elementName -small -green -active">
  Submit
</button>

Viel sauberer, oder? Es ist viel einfacher zu sehen, was diese Modifier-Klassen aussagen sollen, ohne all den repetitiven Kram, der im Weg ist.

Beim Untersuchen eines Elements mit den Browser-Entwicklertools sehen Sie immer noch die vollständige Regel im Styling-Panel, sodass die Verbindung zur ursprünglichen Komponente auf diese Weise erhalten bleibt

.a-blockName__elementName.-green {
  background: green;
  color: white;
}

Es ist nicht viel anders als das BEM-Äquivalent

.block-name__element-name--green {
  background: green;
  color: white;
}

Die Verwaltung des Zustands wird einfach

Ein großer Vorteil, den ABEM gegenüber klassischem BEM hat, ist, dass die Verwaltung des Zustands einer Komponente immens einfacher wird. Nehmen wir als Beispiel ein einfaches Akkordeon. Wenn ein Abschnitt dieses Akkordeons geöffnet ist, sagen wir, wir möchten diese Änderungen am Styling vornehmen

  • Ändern Sie die Hintergrundfarbe der Abschnittsüberschrift
  • Zeigen Sie den Inhaltsbereich an
  • Lassen Sie einen Pfeil nach oben zeigen

Wir werden uns für dieses Beispiel strikt an die klassische B__E–M-Syntax halten und die Regel „eine Klasse pro CSS-Selektor“ einhalten. Das ist es, was wir am Ende erhalten (beachten Sie, dass dieses Akkordeon der Kürze halber nicht zugänglich ist)

Siehe Stift Akkordeon 1 – reines BEM von Daniel Tonon (@daniel-tonon) auf CodePen.

Das SCSS sieht ziemlich sauber aus, aber schauen Sie sich all die zusätzlichen Klassen an, die wir dem HTML für nur eine Zustandsänderung hinzufügen müssen!

HTML, während ein Segment mit BEM geschlossen ist

<div class="revealer accordion__section">
  <div class="revealer__trigger">
    <h2 class="revealer__heading">Three</h2>
    <div class="revealer__icon"></div>
  </div>
  <div class="revealer__content">
    Lorem ipsum dolor sit amet...
  </div>
</div>

HTML, während ein Segment mit BEM geöffnet ist

<div class="revealer accordion__section">
  <div class="revealer__trigger revealer__trigger--open">
    <h2 class="revealer__heading">One</h2>
    <div class="revealer__icon revealer__icon--open"></div>
  </div>
  <div class="revealer__content revealer__content--open">
    Lorem ipsum dolor sit amet...
  </div>
</div>

Schauen wir uns nun an, was passiert, wenn wir zu dieser schicken neuen A-B__E -M-Methode wechseln

Siehe Stift Akkordeon 2 – ABEM-Alternative von Daniel Tonon (@daniel-tonon) auf CodePen.

Eine einzige Klasse steuert jetzt das zustandsspezifische Styling für die gesamte Komponente, anstatt dass eine separate Klasse auf jedes Element einzeln angewendet werden muss.

HTML, während ein Segment mit ABEM geöffnet ist

<div class="m-revealer o-accordion__section -open">
  <div class="m-revealer__trigger">
    <h2 class="m-revealer__heading">One</h2>
    <div class="m-revealer__icon"></div>
  </div>
  <div class="m-revealer__content">
    Lorem ipsum dolor sit amet...
  </div>
</div>

Auch, schauen Sie, wie viel einfacher der JavaScript geworden ist. Ich habe den JavaScript so sauber wie möglich geschrieben und das war das Ergebnis

JavaScript bei Verwendung von reinem BEM

class revealer {
  constructor(el){
    Object.assign(this, {
      $wrapper: el,
      targets: ['trigger', 'icon', 'content'],
      isOpen: false,
    });
    this.gather_elements();
    this.$trigger.onclick = ()=> this.toggle();
  }

  gather_elements(){
    const keys = this.targets.map(selector => `$${selector}`);
    const elements = this.targets.map(selector => {
      return this.$wrapper.querySelector(`.revealer__${selector}`);
    });
    let elObject = {};
    keys.forEach((key, i) => {
      elObject[key] = elements[i];
    });
    Object.assign(this,	elObject);
  }
  
  toggle(){
    if (this.isOpen) {
      this.close();
    } else {
      this.open();
    }
  }

  open(){
    this.targets.forEach(target => {
      this[`$${target}`].classList.add(`revealer__${target}--open`);
    })
    this.isOpen = true;
  }

  close(){
    this.targets.forEach(target => {
      this[`$${target}`].classList.remove(`revealer__${target}--open`);
    })
    this.isOpen = false;
  }
}

document.querySelectorAll('.revealer').forEach(el => {
  new revealer(el);
})

JavaScript bei Verwendung von ABEM

class revealer {
  constructor(el){
    Object.assign(this, {
      $wrapper: el,
      isOpen: false,
    });
    this.$trigger = this.$wrapper.querySelector('.m-revealer__trigger');
    this.$trigger.onclick = ()=> this.toggle();
  }
  
  toggle(){
    if (this.isOpen) {
      this.close();
    } else {
      this.open();
    }
  }

  open(){
    this.$wrapper.classList.add(`-open`);
    this.isOpen = true;
  }

  close(){
    this.$wrapper.classList.remove(`-open`);
    this.isOpen = false;
  }
}

document.querySelectorAll('.m-revealer').forEach(el => {
  new revealer(el);
})

Dies war nur ein sehr einfaches Akkordeon-Beispiel. Denken Sie darüber nach, was passiert, wenn Sie dies auf etwas wie eine Sticky-Header erweitern, die sich beim Sticky-Sein ändert. Ein Sticky-Header muss möglicherweise 5 verschiedenen Komponenten mitteilen, wann der Header sticky ist. Dann müssen in jeder dieser 5 Komponenten möglicherweise 5 Elemente auf den Sticky-Header reagieren. Das sind 25 element.classList.add("[componentName]__[elementName]--sticky") Regeln, die wir in unserer JS schreiben müssten, um die BEM-Namenskonvention strikt einzuhalten. Was ist sinnvoller? 25 eindeutige Klassen, die auf jedes betroffene Element angewendet werden, oder nur eine -sticky Klasse, die auf den Header angewendet wird und auf die alle 5 Elemente in allen 5 Komponenten leicht zugreifen und sie lesen können?

Die BEM-„Lösung“ ist völlig unpraktisch. Die Anwendung von Modifier-Styling auf große, komplexe Komponenten wird zu einem Graubereich. Ein Graubereich, der zu Verwirrung bei allen Entwicklern führt, die versuchen, sich strikt an die BEM-Namenskonvention zu halten.

ABEM-Modifier-Probleme

Die Trennung des Modifiers ist nicht ohne ihre Tücken. Es gibt jedoch einige einfache Möglichkeiten, diese Tücken zu umgehen.

Problem 1: Verschachtelung

Also haben wir unser Akkordeon und es funktioniert perfekt. Später möchte der Kunde ein zweites Akkordeon in das erste verschachteln. Also tun Sie das… das passiert

Siehe Stift Akkordeon 3 – ABEM-Verschachtelungsfehler von Daniel Tonon (@daniel-tonon) auf CodePen.

Das Verschachteln eines zweiten Akkordeons in das erste verursacht einen ziemlich problematischen Fehler. Das Öffnen des übergeordneten Akkordeons wendet die Open-State-Styling auch auf alle untergeordneten Akkordeons in diesem Segment an.

Das ist etwas, das Sie offensichtlich nicht wollen. Es gibt aber einen guten Weg, dies zu vermeiden.

Um es zu erklären, spielen wir ein kleines Spiel. Angenommen, diese beiden CSS-Regeln sind auf dasselbe Element angewendet, welche Farbe denken Sie, würde der Hintergrund dieses Elements haben?

.-green > * > * > * > * > * > .element {
  background: green;
}

.element.-blue {
  background: blue;
}

Wenn Sie Grün sagen, wegen der ersten Regel, die eine höhere Spezifität als die zweite Regel hat, würden Sie tatsächlich falsch liegen. Sein Hintergrund wäre blau.

Fun Fact: * ist der niedrigste Spezifitätsselektor in CSS. Er bedeutet im Grunde „alles“ in CSS. Er hat eigentlich keine Spezifität, was bedeutet, dass er einem Selektor, den Sie ihm hinzufügen, keine Spezifität hinzufügt. Das bedeutet, selbst wenn Sie eine Regel verwenden, die aus einer einzelnen Klasse und 5 Sternen besteht (.element > * > * > * > * > *), könnte sie trotzdem leicht durch eine einzelne Klasse in der nächsten CSS-Zeile überschrieben werden!

Wir können uns diesen kleinen CSS-Trick zunutze machen, um einen zielgerichteteren Ansatz für den Akkordeon-SCSS-Code zu erstellen. Dies ermöglicht es uns, unsere Akkordeons sicher zu verschachteln.

Siehe Stift Akkordeon 4 – ABEM-Verschachtelungsfehler behoben von Daniel Tonon (@daniel-tonon) auf CodePen.

Durch die Verwendung des .-modifierName > * > & Musters können Sie direkte Nachkommen ansprechen, die mehrere Ebenen tief sind, ohne dass Ihre Spezifität außer Kontrolle gerät.

Ich verwende diese direkte Zieltechnik nur, wenn sie notwendig wird. Standardmäßig schreibe ich ABEM, wie im ursprünglichen ABEM-Akkordeon-Beispiel. Die nicht-zielgerichtete Methode ist in den meisten Fällen ausreichend. Das Problem mit dem zielgerichteten Ansatz ist, dass das Hinzufügen eines einzigen Wrappers um etwas das gesamte System potenziell brechen kann. Der nicht-zielgerichtete Ansatz leidet nicht unter diesem Problem. Er ist viel nachgiebiger und verhindert, dass die Stile brechen, wenn Sie das HTML später ändern müssen.

Problem 2: Namenskollisionen

Ein Problem, auf das Sie bei der Verwendung der nicht-zielgerichteten Modifier-Technik stoßen können, sind Namenskollisionen. Nehmen wir an, Sie müssen eine Reihe von Tabs erstellen und jeder Tab enthält ein Akkordeon. Beim Schreiben dieses Codes haben Sie sowohl die Akkordeons als auch die Tabs auf die -active Klasse reagieren lassen. Dies führt zu einer Namenskollision. Alle Akkordeons im aktiven Tab werden ihre aktiven Stile angewendet. Dies liegt daran, dass alle Akkordeons Kinder der Tab-Container-Elemente sind. Es sind die Tab-Container-Elemente, auf die die tatsächliche -active Klasse angewendet wird. (Weder die Tabs noch das Akkordeon im folgenden Beispiel sind aus Gründen der Kürze zugänglich.)

Siehe Stift Akkordeon in Tabs 1 – fehlerhaft von Daniel Tonon (@daniel-tonon) auf CodePen.

Nun, eine Möglichkeit, diesen Konflikt zu lösen, wäre, das Akkordeon einfach auf eine -open Klasse anstatt auf eine -active Klasse reagieren zu lassen. Ich würde diesen Ansatz empfehlen. Der Einfachheit halber nehmen wir jedoch an, dass dies keine Option ist. Sie könnten die oben erwähnte direkte Zieltechnik verwenden, aber das macht Ihre Stile sehr brüchig. Stattdessen können Sie den Komponentennamen vor dem Modifier hinzufügen, wie hier

.o-componentName {
  &__elementName {
    .-componentName--modifierName & {
      /* modifier styles go here */
    }
  }
}

Der Bindestrich am Anfang des Namens signalisiert immer noch, dass es sich um eine Modifier-Klasse handelt. Der Komponentnename verhindert Namensraumkollisionen mit anderen Komponenten, die nicht betroffen sein sollten. Der doppelte Bindestrich ist hauptsächlich eine Anspielung auf die klassische BEM-Modifier-Syntax, um noch einmal zu bekräftigen, dass es sich um eine Modifier-Klasse handelt.

Hier ist das Akkordeon und Tab-Beispiel erneut, diesmal jedoch mit der Namensraum-Korrektur

Siehe Stift Akkordeon in Tabs 2 – korrigiert von Daniel Tonon (@daniel-tonon) auf CodePen.

Ich empfehle jedoch nicht, diese Technik standardmäßig zu verwenden, hauptsächlich um das HTML sauber zu halten und Verwirrung zu vermeiden, wenn mehrere Komponenten denselben Modifier gemeinsam nutzen müssen.

Die meiste Zeit wird eine Modifier-Klasse verwendet, um eine Zustandsänderung wie im obigen Akkordeon-Beispiel anzuzeigen. Wenn sich ein Element ändert, sollten alle Kindelemente, unabhängig von der Komponente, zu der sie gehören, diese Zustandsänderung lesen und leicht darauf reagieren können. Wenn eine Modifier-Klasse mehrere Komponenten gleichzeitig beeinflussen soll, kann es zu Verwirrung darüber kommen, zu welcher Komponente dieser Modifier speziell gehört. In diesen Fällen schadet die Namensraumierung des Modifiers mehr, als sie nützt.

Zusammenfassung der ABEM-Modifier-Technik

Um die ABEM-Modifier optimal zu nutzen, verwenden Sie standardmäßig die Syntax .-modifierName & oder &.-modifierName (hängt davon ab, welches Element die Klasse hat).

.o-componentName {
  &.-modifierName {
    /* componentName modifier styles go here */
  }

&__elementName {
    .-modifierName & {
      /* elementName modifier styles go here */
    }
  }
}

Verwenden Sie direkte Zielerfassung, wenn die Verschachtelung einer Komponente in sich selbst ein Problem verursacht.

.o-componentName {
  &__elementName {
    .-nestedModifierName > * > & {
      /* modifier styles go here */
    }
  }
}

Verwenden Sie den Komponentennamen im Modifier, wenn Sie gemeinsame Modifier-Namenskollisionen antreffen. Tun Sie dies nur, wenn Ihnen kein anderer Modifier-Name einfällt, der immer noch sinnvoll ist.

.o-componentName {
  &__elementName {
    .-componentName--sharedModifierName & {
      /* modifier styles go here */
    }
  }
}

Kontextsensitive Stile

Ein weiteres Problem bei der strikten Einhaltung der BEM-Methode „eine Klasse pro Selektor“ ist, dass sie das Schreiben von kontextsensitiven Stilen nicht zulässt.

Kontextsensitive Stile sind im Grunde „wenn dieses Element innerhalb dieses Elternteils liegt, wende diese Stile darauf an“.

Bei kontextsensitiven Stilen gibt es eine Elternkomponente und eine Kindkomponente. Die Elternkomponente sollte diejenige sein, die Layout-bezogene Stile wie Ränder und Positionierung auf die Kindkomponente anwendet (.parent .child { margin: 20px }). Die Kindkomponente sollte standardmäßig immer keinen Rand außerhalb der Komponente haben. Dies ermöglicht die Verwendung von Kindkomponenten in mehr Kontexten, da die Eltern für ihr eigenes Layout verantwortlich sind und nicht ihre Kinder.

Genau wie bei echter Elternschaft sind die Eltern diejenigen, die die Kontrolle haben sollten. Sie sollten Ihre unartigen, ahnungslosen Kinder nicht die Entscheidungen treffen lassen, wenn es um das Layout der Eltern geht.

Um dieses Konzept weiter zu vertiefen, tun wir so, als würden wir eine brandneue Website erstellen und gerade die Abonnement-Formular-Komponente für die Website erstellen.

Siehe Stift Kontextsensitiv 1 – IE-unfreundlich von Daniel Tonon (@daniel-tonon) auf CodePen.

Dies ist das erste Mal, dass wir ein Formular auf dieser großartigen neuen Website erstellen mussten, die wir bauen. Wir wollen wie all die coolen Kids sein, also haben wir CSS Grid für das Layout verwendet. Wir sind aber schlau. Wir wissen, dass das Button-Styling an vielen weiteren Stellen auf der Website verwendet wird. Um uns darauf vorzubereiten, trennen wir die Abonnement-Button-Stile in eine eigene Komponente, wie gute kleine Entwickler.

Eine Weile später beginnen wir mit dem plattformübergreifenden Testen. Wir öffnen IE11 und sehen nur dieses hässliche Ding, das uns ins Gesicht starrt

IE11 unterstützt CSS Grid zwar irgendwie, aber es unterstützt grid-gap oder Auto-Platzierung nicht. Nach einigen kathartischen Schimpftiraden und dem Wunsch, dass die Leute ihre Browser aktualisieren, passen Sie die Stile an, sodass sie eher so aussehen

Siehe Stift Kontextsensitiv 2 – was man nicht tun sollte von Daniel Tonon (@daniel-tonon) auf CodePen.

Jetzt sieht es in IE perfekt aus. Alles ist richtig in der Welt. Was könnte schief gehen?

Ein paar Stunden später setzen Sie diesen Button-Component in eine andere Component auf der Website. Diese andere Component legt ihre Kinder ebenfalls mit CSS-Grid an.

Sie schreiben folgenden Code

Siehe Stift Kontextsensitiv 3 – die andere Komponente von Daniel Tonon (@daniel-tonon) auf CodePen.

Sie erwarten, dass ein Layout wie dieses auch in IE11 angezeigt wird

Aber stattdessen, wegen des früher geschriebenen grid-column: 3; Codes, sieht es am Ende so aus

Yikes! Was machen wir also mit diesem grid-column: 3; CSS, das wir früher geschrieben haben? Wir müssen es auf die Elternkomponente beschränken, aber wie sollen wir das machen?

Nun, die klassische BEM-Methode, dies zu behandeln, ist, eine neue Elternkomponenten-Elementklasse zum Button hinzuzufügen, wie hier

Siehe Stift Kontextsensitiv 4 – klassische BEM-Lösung von Daniel Tonon (@daniel-tonon) auf CodePen.

Oberflächlich betrachtet sieht diese Lösung ziemlich gut aus

  • Sie hält die Spezifität niedrig
  • Die Elternkomponente kontrolliert ihr eigenes Layout
  • Die Formatierung wird wahrscheinlich nicht in andere Komponenten überschwappen, in die wir sie nicht haben wollen.

Alles ist großartig und alles ist richtig in der Welt… richtig?

Der Nachteil dieses Ansatzes liegt hauptsächlich darin, dass wir dem Button-Component eine zusätzliche Klasse hinzufügen mussten. Da die subscribe-form__submit Klasse in der Basis-button-Komponente nicht existiert, bedeutet dies, dass wir zusätzliche Logik zu dem hinzufügen müssen, was wir als unsere Template-Engine verwenden, damit sie die richtigen Stile erhält.

Ich liebe es, Pug zur Generierung meiner Seitenvorlagen zu verwenden. Ich zeige Ihnen, was ich meine, anhand von Pug-Mixins als Beispiel.

Zuerst ist hier der ursprüngliche IE-unfreundliche Code, umgeschrieben im Mixin-Format

Siehe Stift Kontextsensitiv 5 – IE-unfreundlich mit Mixins von Daniel Tonon (@daniel-tonon) auf CodePen.

Fügen wir nun diese IE 11 subscribe-form__submit Klasse hinzu

Siehe Stift Kontextsensitiv 6 – IE-sichere BEM-Lösung mit Mixins von Daniel Tonon (@daniel-tonon) auf CodePen.

Das war gar nicht so schwer, also was beschwere ich mich? Nun, sagen wir jetzt, wir wollen dieses Modul manchmal in einer Seitenleiste platzieren. Wenn wir das tun, wollen wir, dass das E-Mail-Eingabefeld und der Button übereinander gestapelt werden. Denken Sie daran, dass wir gemäß BEM nichts höher als eine einzelne Klasse in unseren Stilen verwenden dürfen.

Siehe Stift Kontextsensitiv 7 – IE-sicher BEM mit Mixins in der Seitenleiste von Daniel Tonon (@daniel-tonon) auf CodePen.

Dieser Pug-Code sieht jetzt nicht mehr so einfach aus, oder? Es gibt ein paar Dinge, die zu diesem Durcheinander beitragen.

  1. Container-Abfragen würden dieses Problem erheblich reduzieren, aber sie existieren noch nicht nativ in irgendeinem Browser
  2. Die Probleme rund um die BEM-Modifier-Syntax tauchen wieder auf.

Versuchen wir es jetzt noch einmal, aber diesmal mit kontextsensitiven Stilen

Siehe Stift Kontextsensitiv 8 – IE-sichere kontextsensitive mit Mixins in der Seitenleiste von Daniel Tonon (@daniel-tonon) auf CodePen.

Schauen Sie, wie viel einfacher die Pug-Markup geworden ist. Es gibt keine „Wenn dies, dann das“-Logik, um die sich in der Pug-Markup Sorgen gemacht werden müssen. All diese übergeordnete Logik wird an das CSS übergeben, das sowieso besser versteht, welche Elemente Eltern anderer Elemente sind.

Sie haben vielleicht bemerkt, dass ich im letzten Beispiel einen Selektor verwendet habe, der drei Klassen tief war. Er wurde verwendet, um dem Button 100% Breite zu geben. Ja, ein dreiklassen-Selektor ist in Ordnung, wenn Sie ihn rechtfertigen können.

Ich wollte nicht, dass 100% Breite dem Button jedes Mal angewendet wird, wenn er

  • irgendwo verwendet wird
  • im Abonnementformular platziert ist
  • in der Seitenleiste platziert ist

Ich wollte nur 100% Breite anwenden, wenn er sowohl im Abonnementformular als auch in der Seitenleiste platziert war. Der beste Weg, damit umzugehen, war ein dreiklassen-Selektor.

Ok, in Wirklichkeit würde ich eher eine ABEM-ähnliche -verticalStack Modifier-Klasse für das subscribe-form Element verwenden, um die vertikalen Stack-Stile anzuwenden, oder vielleicht sogar über Element-Abfragen mit EQCSS. Dies würde bedeuten, dass ich die vertikalen Stack-Stile in mehr Situationen als nur in der Seitenleiste anwenden könnte. Der Einfachheit halber habe ich es jedoch als kontextsensitive Stile gemacht.

Nachdem wir nun kontextsensitive Stile verstehen, kehren wir zum ursprünglichen Beispiel zurück und verwenden kontextsensitive Stile, um die problematische grid-column: 3 Regel anzuwenden

Siehe Stift Kontextsensitiv 9 – kontextsensitives Verfahren mit Mixins von Daniel Tonon (@daniel-tonon) auf CodePen.

Kontextsensitive Stile führen zu einfacherer HTML- und Template-Logik und behalten gleichzeitig die Wiederverwendbarkeit von Kindkomponenten bei. Die Philosophie von BEM mit einer Klasse pro Selektor erlaubt dies jedoch nicht.

Da kontextsensitive Stile hauptsächlich mit dem Layout zu tun haben, sollten Sie sie je nach Umständen im Allgemeinen immer dann verwenden, wenn Sie sich mit diesen CSS-Eigenschaften beschäftigen

  • Alles CSS-Grid-bezogene, das auf das Kindelement angewendet wird (grid-column, grid-row etc.)
  • Alles Flexbox-bezogene, das auf das Kindelement angewendet wird (flex-grow, flex-shrink, align-self etc.)
  • margin-Werte größer als 0
  • position-Werte außer relative (zusammen mit den Eigenschaften top, left, bottom und right)
  • transform, wenn es zur Positionierung verwendet wird, wie translateY

Sie können diese Eigenschaften auch in kontextsensitive Stile einfügen, aber sie werden seltener kontextsensitiv benötigt.

  • width
  • height
  • padding
  • border

Um ganz klar zu sein, kontextsensitive Stile sind nicht zum Verschachteln um des Verschachtelns willen da. Sie müssen sie sich vorstellen, als würden Sie eine if-Anweisung in JavaScript schreiben.

Also für eine CSS-Regel wie diese

.parent .element {
  /* context sensitive styles */
}

Sie sollten sich vorstellen, als würden Sie diese Art von Logik schreiben

if (.element in .parent) {
  .element { /* context sensitive styles */ }
}

Verstehen Sie auch, dass das Schreiben einer Regel, die drei Ebenen tief ist wie diese

.grandparent .parent .element {
  /* context sensitive styles */
}

Sollte sich so vorstellen, als würden Sie Logik wie diese schreiben

if (
    (.element in .parent) &&
    (.element in .grandparent) &&
    (.parent in .grandparent)
  ) {
  .element { /* context sensitive styles */ }
}

Schreiben Sie also bitte einen CSS-Selektor, der drei Ebenen tief ist, wenn Sie wirklich denken, dass Sie dieses Maß an Spezifität benötigen. Verstehen Sie bitte die zugrundeliegende Logik des CSS, das Sie schreiben. Verwenden Sie nur ein Maß an Spezifität, das für die jeweilige Formatierung, die Sie erreichen möchten, sinnvoll ist.

Und noch einmal, nur um ganz klar zu sein, verschachteln Sie nicht um des Verschachtelns willen!

Zusammenfassung

Die Methodik hinter der BEM-Namenskonvention ist etwas, das ich von ganzem Herzen befürworte. Sie erlaubt es, CSS in kleine, leicht zu verwaltende Komponenten zu zerlegen, anstatt CSS in einem unhandlichen Durcheinander von hoher Spezifität zu hinterlassen, das schwer zu warten ist. Die offizielle Syntax für BEM hat jedoch viel zu wünschen übrig.

Die offizielle BEM-Syntax

  • Unterstützt kein Atomic Design
  • Ist nicht leicht erweiterbar
  • Braucht länger, bis Ihr Gehirn die Gruppierung der Klassennamen verarbeitet hat
  • Ist furchtbar inkompetent, wenn es um die Verwaltung von Zuständen auf großen Komponenten geht
  • Ermutigt Sie, Einzelklassen-Selektoren zu verwenden, obwohl Doppelkeklassen-Selektoren zu einfacherer Wartbarkeit führen
  • Versucht, alles zu benennen, selbst wenn die Namensraumierung mehr Probleme verursacht, als sie löst.
  • Macht HTML extrem aufgebläht, wenn es richtig gemacht wird

Mein inoffizieller ABEM-Ansatz

  • Erleichtert die Arbeit mit Atomic Design
  • Gibt das Bindestrichzeichen als zusätzliche Methode zur Gruppierung frei
  • Ermöglicht es Ihrem Gehirn, die Gruppierung der Klassennamen schneller zu verarbeiten
  • Ist hervorragend geeignet, um Zustände auf Komponenten jeder Größe zu handhaben, egal wie viele Unterkomponenten sie hat
  • Fördert kontrollierte Spezifität anstatt nur niedrige Spezifität, um Teamverwirrung zu vermeiden und die Wartbarkeit der Website zu verbessern
  • Vermeidet Namensraumierung, wenn sie nicht benötigt wird
  • Hält HTML mit minimalen zusätzlichen Klassen für Module recht sauber und behält dabei alle Vorteile von BEM bei

Haftungsausschluss

Ich habe die Idee des -modifier (einzelner Bindestrich vor dem Modifikatornamen) nicht erfunden. Ich habe sie 2016 entdeckt, als ich einen Artikel las. Ich erinnere mich nicht mehr, wer die Idee ursprünglich konzipiert hat. Ich werde sie gerne anerkennen, wenn jemand den Artikel kennt.

Update: 21. Januar 2018 (Kommentarantwort)

Niemand konnte den genauen Artikel verlinken, von dem ich über die -modifier Syntax gelernt habe. Was ich sagen kann ist, dass ich es gelernt habe, indem ich einen Artikel über BEVM (Block__Element–Variation -Modifier) gelesen habe.

Hier sind einige andere Leute, die die -modifier Syntax vor mir entwickelt haben

BEVM kann immer noch mit ABEM funktionieren, wenn Ihnen diese Methodik gefällt (was es zu ABEVM macht). Nachdem ich die -modifier Syntax eine Weile verwendet habe, habe ich die &--modifier Syntax schließlich ganz aufgegeben. Ich konnte keinen wirklichen Vorteil darin sehen, den doppelten Bindestrich beizubehalten, wenn der einfache Bindestrich in meinem CSS einfacher zu verwenden war und mein HTML viel sauberer machte.

Einige Leute haben BEMIT als sehr ähnlich bezeichnet. Sie haben Recht, es teilt einige Ähnlichkeiten mit BEMIT, hat aber auch einige Unterschiede.

Sie könnten ABEM und BEMIT bis zu einem gewissen Grad zusammenführen. Ich habe Leute gehört, die sagen, dass sie das explizite "is" der zustandsbasierten Klassen in BEMIT bevorzugen (z.B. .is-active). Das ist absolut in Ordnung, wenn Sie das "is" zu ABEM hinzufügen möchten, würde ich empfehlen, den Modifier so zu schreiben: .-is-modifierName. Sehen Sie, was ich mit camelCase meine, das benutzerdefinierte Gruppierungen ermöglicht?

Die Utilities können ebenfalls recht einfach aus BEMIT übernommen werden, sie würden immer noch als .u-utilityName geschrieben werden. Die „object“- und „component“-Namensräume in BEMIT würden nicht übernommen werden. Sie würden durch die Atomic Design-Namensräume in ABEM ersetzt werden.

Eine interessante Diskussion in den Kommentaren war die Verwendung der @extend-Funktionalität in Sass. Zum Beispiel die Verwendung von <button class='subscribe-form__submit'></button> und .subscribe-form__submit { @extend .button; grid-column: 3; }. Ich denke, kontextsensitive Stile sind der bessere Weg. Ich bin ziemlich stark gegen diese Implementierung von @extend, es sei denn, das CMS zwingt Sie dazu. Die vollständige Diskussion und meine Antwort finden Sie hier: https://css-tricks.de/abem-useful-adaptation-bem/#comment-1613824.

Eine Sache, mit der viele Leute Probleme hatten, war, dass ich nicht sehr tief auf das Konzept von Atomic Design eingegangen bin und wie man es verwendet. Es lag außerhalb des Rahmens dieses Artikels. Wenn ich versuchen würde, tief darauf einzugehen, wie Komponenten mit Atomic Design-Prinzipien kategorisiert werden, hätte dies leicht die Länge des Artikels verdoppelt (und dieser Artikel ist bereits sehr lang). Ich habe genug von einer Zusammenfassung gegeben, um das Konzept von Atomic Design einzuführen, und ich habe auf eine Ressource verlinkt, die viel tiefer in das Thema eintaucht. Das war ungefähr so viel Aufmerksamkeit, wie ich dem Erklären von Atomic Design widmen wollte.

Da die Kategorisierung von Atomic Design ein so verwirrendes Thema ist, habe ich vor, einen Artikel zu schreiben, der sich ausschließlich damit beschäftigt, wie man Komponenten von Atomic Design kategorisiert. Ich werde versuchen, klare Richtlinien zu erstellen, um herauszufinden, zu welcher Atomic Design-Kategorie eine bestimmte Komponente gehört. Erwarten Sie ihn jedoch nicht so bald. Es wird noch eine Weile dauern, bis er veröffentlicht wird.

Update: 2. April 2019 (Atomic-Kategorisierungswerkzeug)

Diese Pläne, einen Artikel über Atomic Design zu schreiben, wurden nie umgesetzt. Ich begann, einen zu planen, aber mir wurde klar, dass das Schreiben eines Artikels, um all die kleinen Dinge zu erklären, die bei der Kategorisierung von Modulen zu berücksichtigen sind, der falsche Weg war. Ein Werkzeug, das am Ende eine Empfehlung gibt, wäre weitaus nützlicher.

Also machte ich mich an die Arbeit und freue mich, den Start meines brandneuen Atomic Categorizer Tools ankündigen zu können!

Es ist ein einfaches Quiz, das Ihnen Punkte für jede Kategorie gibt, während Sie die Fragen beantworten. Die Fragen und Punkte sind vielleicht nicht 100% perfekt, aber ich denke, es macht die Kategorisierung der Komponenten meistens ziemlich gut. Probieren Sie es aus, es macht viel mehr Spaß, als ein paar tausend Wörter Text zu lesen. 😊