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!

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.


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.
- Wir beginnen damit, unseren Standardabstand für Elemente mit
margin-bottomfestzulegen. - Als Nächstes beabstanden wir unsere „Abschnitte“ mit
margin-top– d. h. sehr großer Abstand über jeder Überschrift. - 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
h1zur Vollständigkeit aufgenommen. Normalerweise würde ich einem CMS-Benutzer nicht erlauben, über WYSIWYG eineh1hinzuzufü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!
Sie haben so informative Inhalte geteilt, machen Sie weiter so und danke für das Teilen dieses wertvollen Materials mit uns.
Vielen Dank :) Ich werde es versuchen!
Ein Kollege, der Firefox nutzt, hat darauf hingewiesen, dass
:has(* +)nicht funktioniert, selbst mit aktiviertem:has()Support-Flag.Es scheint, dass dies ein Bug hier sein könnte: https://bugzilla.mozilla.org/show_bug.cgi?id=1774588
Hoffentlich beheben die Firefox-Entwickler dies, bevor sie es wirklich veröffentlichen. Da viele der cooleren :has()-Demos auf diese Art von Selektoren angewiesen sind, bin ich ziemlich zuversichtlich, dass sie das tun werden :)
Korrekt. Um die Unterstützung in Ihrem Code zu erkennen, müssen Sie daher
:has(+ *)anstelle von:has(*)verwenden.Siehe https://brm.us/feature-detect-has für Details.
Fantastischer Artikel. Warte auf die volle Browserunterstützung für :where, :is und :has. Kann es kaum erwarten
Dies ist eine Lösung für ein Problem, das nur durch die Verwendung von
margin-bottomanstelle vonmargin-topverursacht wird, und die Begründung dafür ist „Aber persönlich würde ich lieber margin-bottom für den Abstand langer Texte festlegen“. Das Beispiel „Listenpunktabstand“ ist der offensichtlichste Fall: Wechseln Sie zumargin-topund alles, was Sie brauchen, ist ein einfachesli + li.:haswird in vielen Szenarien äußerst nützlich sein. Dies ist keiner davon, es sei denn, Sie beschließen absichtlich, die Richtung Ihres Rands zu ändern. Lassen wir die Benutzer nicht ohne guten Grund zurück.Ich habe anderswo eine detaillierte Antwort geschrieben.
Danke, Shiv, für deine sehr detaillierte Antwort. Dies ist wahrscheinlich das erste Mal, dass jemand einen Blogbeitrag über etwas schreibt, das ich gesagt habe. Du wirfst einige gute Punkte auf, auf die ich hier versuchen werde einzugehen.
Verwendung von Margin-Top.
Ich habe in der Vergangenheit den lobotomisierten Eulen-Selektor verwendet. Ich fand ihn jedoch nie standortweit nützlich (gemäß dem Originalbeitrag: https://alistapart.com/article/axiomatic-css-and-lobotomized-owls/), da die Art von Designs, die mir übergeben werden, die meisten Elemente standardmäßig nicht für vertikalen Abstand prädestinieren. Wenn ich ihn also verwendet habe, war er immer eingegrenzt. Und in einem solchen Fall war es die
.rich-text-Eingrenzung, d. h. der Abstand für lange Texte. Ich denke, das ist eine völlig in Ordnung Lösung (wenn auch nicht ohne Nachteile – siehe „Starre Struktur“).Browserunterstützung.
Sie weisen darauf hin, dass :has() nicht gut unterstützt wird und mein Code nicht fehlerverzeihend abfällt. Keine Einwände hierzu. Alles, was ich dazu sagen kann, ist, dass ich in meinem Artikel – zweimal – auf die Browserunterstützung hingewiesen habe. Wie alle :has()-Demos sollte es noch nicht in der Produktion verwendet werden.
Selektorkomplexität.
Ich stimme zu, dass meine Lösung komplex ist (die zusätzliche Spezifitätsbehandlung hilft da nicht). Ich habe versucht, sie mit Code-Kommentaren weniger komplex zu machen – ob erfolgreich oder nicht, weiß ich nicht.
Aber ich persönlich glaube nicht, dass dieser Block weniger komplex ist
Aber vielleicht bin das nur ich.
Ich stelle fest, dass Sie den Abschnitt „Starre Struktur“ (d. h. die Verwendung eines
.rich-text-Scopes) nicht angesprochen haben. Ich glaube, dies ist der attraktivste Vorteil meines vorgeschlagenen Ansatzes. Zumindest für die meisten Websites, mit denen ich arbeite. Sicherzustellen, dass der Wrapper das direkte Elternteil aller Prosa-Elemente ist (um zusätzlichen oberen/unteren Abstand zu entfernen), wenn der Inhalt eine Mischung aus CMS- und hartkodiertem Inhalt ist, kann mühsam sein.Wenn ich mir zum Beispiel Ihren Blog ansehe, scheint er ein wirklich guter Kandidat für den lobotomisierten Eulen-Ansatz zu sein. D. h. flaches DOM, einspaltig, textlastig. Aber das ist nicht der Fall für die meisten Websites, mit denen ich arbeite.
Die lobotomisierte Eule ist eine Lösung, und oft eine ziemlich gute. Mein Artikel soll eine Alternative zu dem sein, was ich heute typischerweise sehe. Außerdem denke ich, dass, sobald :has() zum Standard wird (und gut unterstützt wird) und Entwickler sich damit wohler fühlen, wir es mehr in Fällen wie langen Texten sehen werden, da es Ihnen meiner Meinung nach eine feinere Kontrolle gibt.
Ich muss Shiv hier zustimmen. Ich habe mich gut ohne
:has()zurechtgefunden, indem ich mich an den Richtlinien von Andy Bell orientiert habe. Ich vergesse genau, wo alles beschrieben ist, aber meine Implementierung sieht wie folgt aus (die für h1, h4 und andere Elemente erweitert werden kann)Dieser Artikel ist eine unterhaltsame Demo, wie man :has() verwendet, eine großartige Lernressource, aber wir können es einfach halten.
Danke für deine Antwort, Christopher. „…eine unterhaltsame Demo, wie man :has() verwendet […] eine großartige Lernressource“ ist genau das, was ich angestrebt habe :D
Ich bin auch ein Bewunderer von Andy Bells Arbeit in diesem Bereich, insbesondere der Skalierung von Ansichtsfenstergröße/Abstand.
Ich bezweifle, dass mein Ansatz sich jemals weiter durchsetzen wird (oder sollte). Er ist im Moment sehr experimentell, in dem Sinne, dass a) :has() noch nicht vollständig unterstützt wird und b) ich dies noch nicht verwendet habe (außerhalb von Codepen).
Ich denke jedoch, dass das Fehlen eines Wrappers (in Ihrem Fall
.flow) in einigen Fällen sehr überzeugend sein könnte. Aber wie nützlich das ist, wird definitiv je nach Projekt variieren.Was die Einfachheit betrifft, würde ich wahrscheinlich einen vollständig ausgearbeiteten Vergleich beider Ansätze benötigen, bevor ich zustimmen könnte. D. h.: beide Ansätze berücksichtigen alle/die meisten „Flow“-Elemente, beide Ansätze schneiden den oberen und unteren Abstand am Anfang/Ende des Blocks ab, beide Ansätze behandeln den Abstand der Überschriften mit und ohne gestapelte Überschriften, die sich nach Überschriftenebene unterscheiden. Möglicherweise haben Sie Recht.