Gelöst mit :has(): Vertikaler Abstand in langen Texten 

Avatar of Liam Johnston
Liam Johnston am

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

Wenn Sie jemals an Websites mit viel langem Text gearbeitet haben – insbesondere an CMS-Websites, auf denen Personen schier endlose Texte in einem WYSIWYG-Editor eingeben können – mussten Sie wahrscheinlich CSS schreiben, um den vertikalen Abstand zwischen verschiedenen typografischen Elementen wie Überschriften, Absätzen, Listen und so weiter zu verwalten.

Es ist überraschend schwierig, dies richtig hinzubekommen. Und das ist ein Grund, warum Dinge wie das Tailwind Typography Plugin und Stack Overflow’s Prose existieren – obwohl diese viel mehr als nur vertikalen Abstand handhaben.

Firefox unterstützt :has() zum Zeitpunkt der Verfassung hinter dem layout.css.has-selector.enabled Flag in about:config.

Was macht den vertikalen typografischen Abstand kompliziert?

Sicherlich sollte es so einfach sein zu sagen, dass jedes Element – p, h2, ul, etc. – etwas oberen und/oder unteren Rand hat… oder? Leider ist das nicht der Fall. Betrachten Sie dieses gewünschte Verhalten:

  • Die ersten und letzten Elemente eines langen Textblocks sollten keinen zusätzlichen Platz darüber oder darunter (jeweils) haben. Dies dient dazu, dass andere, nicht-typografische Elemente weiterhin vorhersehbar um den langen Inhalt herum platziert werden können.
  • Abschnitte innerhalb des langen Textes sollten einen schönen großen Abstand zwischen sich haben. Ein „Abschnitt“ ist eine Überschrift und aller nachfolgende Inhalt, der zu dieser Überschrift gehört. In der Praxis bedeutet dies einen schönen großen Abstand vor einer Überschrift… aber nicht, wenn diese Überschrift unmittelbar von einer anderen Überschrift gefolgt wird!
Example of a Heading 3 following a paragraph and another following a Heading 2.
Wir möchten mehr Abstand über der Überschrift 3 haben, wenn sie auf ein typografisches Element wie einen Absatz folgt, aber weniger Abstand, wenn sie unmittelbar auf eine andere Überschrift folgt.

Sie müssen nicht weiter als bis hierher zu CSS-Tricks schauen, um zu sehen, wo dies nützlich sein könnte. Hier sind ein paar Screenshots von Abständen, die ich aus einem anderen Artikel entnommen habe.

A Heading 2 element directly above a Heading 3.
Der vertikale Abstand zwischen Überschrift 2 und Überschrift 3
A Heading 3 element directly following a paragraph element.
Der vertikale Abstand zwischen Überschrift 3 und einem Absatz

Die traditionelle Lösung

Die typische Lösung, die ich gesehen habe, besteht darin, beliebige lange Inhalte in ein umgebendes div (oder ein semantisches Tag, falls zutreffend) zu packen. Mein bevorzugter Klassenname war .rich-text, den ich glaube ich als Überbleibsel älterer Versionen von Wagtail CMS verwende, die diese Klasse automatisch beim Rendern von WYSIWYG-Inhalten hinzufügen würden. Tailwind Typography verwendet eine .prose Klasse (plus einige Modifikator-Klassen).

Dann fügen wir CSS hinzu, um alle typografischen Elemente in diesem Wrapper auszuwählen und vertikale Ränder hinzuzufügen. Dabei beachten wir natürlich das oben erwähnte spezielle Verhalten bezüglich gestapelter Überschriften und des ersten/letzten Elements.

Die traditionelle Lösung klingt vernünftig… was ist das Problem? Ich denke, es gibt ein paar…

Starre Struktur

Die Notwendigkeit, eine Wrapper-Klasse wie .rich-text an den richtigen Stellen hinzuzufügen, bedeutet, dass eine bestimmte Struktur in Ihren HTML-Code eingebrannt wird. Das ist manchmal notwendig, aber es fühlt sich an, als müsste es in diesem speziellen Fall nicht sein. Es kann auch leicht vergessen werden, dies überall dort zu tun, wo Sie es benötigen, insbesondere wenn Sie es für eine Mischung aus CMS- und hartkodiertem Inhalt verwenden müssen.

Die HTML-Struktur wird noch starrer, wenn Sie den oberen und unteren Rand des ersten bzw. letzten Elements abschneiden möchten, da diese direkte Kinder des Wrapper-Elements sein müssen, z. B. .rich-text > *:first-child. Dieses > ist wichtig – schließlich möchten wir mit diesem Selektor nicht versehentlich das erste Listenelement in jedem ul oder ol auswählen.

Mischen von Rand-Eigenschaften

In der Welt vor :has() hatten wir keine Möglichkeit, ein Element basierend darauf auszuwählen, was ihm folgt. Daher beinhaltet der traditionelle Ansatz für den Abstand typografischer Elemente eine Mischung aus margin-top und margin-bottom.

  1. Wir beginnen damit, unseren Standardabstand für Elemente mit margin-bottom festzulegen.
  2. Als Nächstes beabstanden wir unsere „Abschnitte“ mit margin-top – d. h. sehr großer Abstand über jeder Überschrift.
  3. Dann überschreiben wir diese großen margin-tops, wenn eine Überschrift unmittelbar von einer anderen Überschrift gefolgt wird, mithilfe des Selektors für nachfolgende Geschwister (z. B. h2 + h3).

Nun, ich weiß nicht, wie es Ihnen geht, aber ich habe immer das Gefühl gehabt, dass es besser ist, eine einzige Randrichtung zum Beabstanden zu verwenden, im Allgemeinen mit Vorliebe für margin-bottom (vorausgesetzt, die CSS gap-Eigenschaft ist nicht machbar, was hier nicht der Fall ist). Ob dies eine große Sache ist oder überhaupt zutrifft, überlasse ich Ihnen. Aber persönlich würde ich lieber margin-bottom für den Abstand langer Texte festlegen.

Kollabierende Ränder

Aufgrund von kollabierenden Rändern ist diese Mischung aus oberen und unteren Rändern per se kein großes Problem. Nur der größere der beiden gestapelten Ränder wirkt, nicht die Summe beider Ränder. Aber… nun ja… ich mag kollabierende Ränder nicht wirklich.

Kollabierende Ränder sind eine weitere Sache, auf die man achten muss. Es kann für Junior-Entwickler verwirrend sein, die mit dieser CSS-Eigenart nicht vertraut sind. Der Abstand ändert sich vollständig (d. h. hört auf zu kollabieren), wenn Sie den Wrapper beispielsweise in ein flex-Layout mit flex-direction: column ändern, was nicht passieren würde, wenn Sie Ihre vertikalen Ränder in einer einzigen Richtung festlegen würden.

Ich weiß mehr oder weniger, wie kollabierende Ränder funktionieren, und ich weiß, dass sie von Design her da sind. Ich weiß auch, dass sie mir gelegentlich das Leben leichter gemacht haben. Aber sie haben es auch schon andere Male schwerer gemacht. Ich finde sie einfach etwas seltsam und würde mich generell lieber darauf verlassen vermeiden.

Die :has() Lösung

Und hier ist mein Versuch, diese Probleme mit :has() zu lösen.

Um die Verbesserungen, die dies bewirken soll, zusammenzufassen:

  • Keine Wrapper-Klasse erforderlich.
  • Wir arbeiten mit einer konsistenten Randrichtung.
  • Kollabierende Ränder werden vermieden (was je nach Standpunkt eine Verbesserung sein kann oder auch nicht).
  • Es werden keine Stile gesetzt und dann sofort überschrieben.

Hinweise und Einschränkungen zur :has() Lösung

  • Prüfen Sie immer die Browserunterstützung. Zum Zeitpunkt der Verfassung unterstützt Firefox :has() nur hinter einem experimentellen Flag.
  • Meine Lösung enthält nicht alle möglichen typografischen Elemente. Zum Beispiel gibt es in meiner Demo keine <blockquote>. Die Liste der Selektoren ist jedoch leicht zu erweitern.
  • Meine Lösung behandelt auch keine nicht-typografischen Elemente, die in Ihren spezifischen langen Textblöcken vorhanden sein können, z. B. <img>. Das liegt daran, dass für die Websites, an denen ich arbeite, wir dazu neigen, das WYSIWYG so weit wie möglich auf reine Textknoten wie Überschriften, Absätze und Listen zu beschränken. Alles andere – z. B. Zitate, Bilder, Tabellen usw. – ist ein separater CMS-Komponentenblock, und diese Blöcke selbst werden beim Rendern auf einer Seite voneinander beabstandet. Aber auch hier kann die Liste der Selektoren erweitert werden.
  • Ich habe nur h1 zur Vollständigkeit aufgenommen. Normalerweise würde ich einem CMS-Benutzer nicht erlauben, über WYSIWYG eine h1 hinzuzufügen, da der Seitentitel irgendwo in der Seitenvorlage fest verdrahtet wäre, anstatt im CMS-Seiteneditor eingegeben zu werden.
  • Ich berücksichtige keine Überschrift, der unmittelbar eine Überschrift desselben Levels folgt (h2 + h2). Dies würde bedeuten, dass die erste Überschrift keinen Inhalt „besitzen“ würde, was eine falsche Verwendung von Überschriften zu sein scheint (und, korrigieren Sie mich, wenn ich falsch liege, aber es könnte WCAG 1.3.1 Info und Beziehungen verletzen). Ich berücksichtige auch keine übersprungenen Überschriftenebenen, die ungültig sind.
  • Ich kritisiere in keiner Weise die bereits erwähnten Ansätze. Wenn ich jemals eine weitere Tailwind-Site baue, werde ich das exzellente Typography-Plugin verwenden, ohne Frage!
  • Ich bin kein Designer. Ich habe diese Abstände durch Augenmaß ermittelt. Sie könnten (und sollten) wahrscheinlich bessere Werte verwenden.

Spezifität und Projektstruktur

Ich wollte hier einen ganzen langen Text darüber schreiben, wie sich die traditionelle Methode und der neue :has()-Ansatz in die ITCSS Methodik einfügen könnten… Aber jetzt, wo wir :where() (den Selektor mit Null-Spezifität) haben, können Sie im Grunde Ihr bevorzugtes Spezifitätsniveau für jeden Selektor wählen.

Dennoch, die Tatsache, dass wir uns nicht mehr mit einem Wrapper befassen – .prose, .rich-text, etc. – lässt es für mich so aussehen, als sollte dies in der „Elemente“-Schicht liegen, d. h. bevor Sie sich mit der Klassenspezifität befassen. Ich habe :where() in meinen Beispielen verwendet, um die Spezifität konsistent zu halten. Alle Selektoren in beiden meiner Beispiele haben einen Spezifitätswert von 0,0,1 (außer dem einfachen Reset).

Zusammenfassung

Da haben Sie es also, eine hochmoderne Lösung für ein sehr langweiliges Problem! Dieser neuere Ansatz ist immer noch nicht das, was ich als „einfaches“ CSS bezeichnen würde – wie ich am Anfang sagte, ist es ein komplexeres Thema, als es auf den ersten Blick scheint. Aber abgesehen von ein paar etwas komplexen Selektoren denke ich, dass der neue Ansatz insgesamt mehr Sinn ergibt und die weniger starre HTML-Struktur sehr ansprechend ist.

Wenn Sie dies oder etwas Ähnliches verwenden, würde ich gerne wissen, wie es bei Ihnen funktioniert. Und wenn Ihnen Möglichkeiten einfallen, es zu verbessern, würde ich das auch gerne hören!