Bei der Blockfragmentierung wird ein CSS-Block-Element (z. B. ein Abschnitt oder Absatz) in mehrere Fragmente aufgeteilt, wenn es nicht vollständig in einen Fragmentcontainer passt, der auch Fragmentor genannt wird. Ein Fragmentierer ist kein Element, sondern eine Spalte in einem mehrspaltigen Layout oder eine Seite in Seitenmedien.
Damit eine Fragmentierung erfolgen kann, müssen sich die Inhalte in einem Fragmentierungskontext befinden. Ein Fragmentierungsumfeld wird am häufigsten durch einen mehrspaltigen Container (Inhalt wird in Spalten aufgeteilt) oder beim Drucken (Inhalt wird in Seiten aufgeteilt) festgelegt. Ein langer Absatz mit vielen Zeilen muss möglicherweise in mehrere Fragmente aufgeteilt werden, sodass die ersten Zeilen im ersten Fragment und die verbleibenden Zeilen in den nachfolgenden Fragmenten platziert werden.
Die Blockfragmentierung entspricht einem anderen bekannten Typ der Fragmentierung: der Linienfragmentierung, auch bekannt als „Zeilenumbruch“. Jedes Inline-Element, das aus mehr als einem Wort besteht (z. B. Textknoten oder <a>
-Elemente), und Zeilenumbrüche zulässt, kann in mehrere Fragmente aufgeteilt werden. Jedes Fragment wird in einem anderen Linienfeld platziert. Ein Line-Box ist die Inline-Fragmentierung, die einem Fragmentator für Spalten und Seiten entspricht.
LayoutNG-Blockfragmentierung
LayoutNGBlockFragmentation ist eine Neufassung der Fragmentierungs-Engine für LayoutNG, die ursprünglich in Chrome 102 eingeführt wurde. In Bezug auf die Datenstrukturen hat es mehrere Vor-NG-Datenstrukturen durch NG-Fragmente ersetzt, die direkt in der Fragmentstruktur dargestellt sind.
So wird jetzt beispielsweise der Wert „avoid“ für die CSS-Eigenschaften „break-before“ und „break-after“ unterstützt. Damit können Autoren Umbrüche direkt nach einem Titel vermeiden. Es sieht oft unschön aus, wenn das letzte Element auf einer Seite eine Überschrift ist, während der Inhalt des Abschnitts auf der nächsten Seite beginnt. Besser ist es, einen Absatz vor der Überschrift einzufügen.
Chrome unterstützt auch den Fragmentierungsüberlauf, sodass monolithische (unzerbrechliche) Inhalte nicht in mehrere Spalten aufgeteilt werden und Maleffekte wie Schatten und Transformationen korrekt angewendet werden.
Blockfragmentierung in LayoutNG ist jetzt abgeschlossen
Die Kernfragmentierung (Blockcontainer, einschließlich Zeilenlayout, Floats und Out-of-Flow-Positionierung) wurde in Chrome 102 eingeführt. Die Flex- und Rasterfragmentierung wurde in Chrome 103 eingeführt und die Tabellenfragmentierung in Chrome 106. Außerdem wurde in Chrome 108 die Funktion Drucken eingeführt. Die Blockfragmentierung war die letzte Funktion, die für die Layoutausführung auf die alte Engine angewiesen war.
Ab Chrome 108 wird die Legacy-Engine nicht mehr für das Layout verwendet.
Außerdem unterstützen LayoutNG-Datenstrukturen das Zeichnen und den Treffertest. Wir nutzen jedoch einige ältere Datenstrukturen für JavaScript-APIs, die Layoutinformationen lesen, z. B. offsetLeft
und offsetTop
.
Wenn Sie alles mit NG layouten, können Sie neue Funktionen implementieren und veröffentlichen, die nur LayoutNG-Implementierungen haben (und keine Legacy-Engine-Entsprechung), z. B. CSS-Containerabfragen, Ankerpositionierung, MathML und benutzerdefiniertes Layout (Houdini). Für Containerabfragen haben wir die Funktion etwas früher veröffentlicht und Entwickler darauf hingewiesen, dass das Drucken noch nicht unterstützt wird.
Wir haben den ersten Teil von LayoutNG 2019 veröffentlicht. Er bestand aus regulärem Blockcontainer-Layout, Inline-Layout, Floats und Out-of-Flow-Positionierung. Flex, Raster und Tabellen wurden jedoch nicht unterstützt. Auch die Blockfragmentierung wurde nicht unterstützt. Für Flexbox, Raster, Tabellen und alles, was die Blockfragmentierung betrifft, würden wir auf die alte Layout-Engine zurückgreifen. Das galt auch für Block-, Inline-, Floating- und Out-of-Flow-Elemente in fragmentierten Inhalten. Wie Sie sehen, ist die Umstellung einer so komplexen Layout-Engine vor Ort ein sehr heikler Tanz.
Außerdem war Mitte 2019 der Großteil der Hauptfunktionen des LayoutNG-Blockfragmentierungslayouts bereits implementiert (hinter einem Flag). Warum hat es so lange gedauert, bis der Artikel versendet wurde? Die kurze Antwort lautet: Fragmentierung muss korrekt mit verschiedenen Legacy-Systemen koexistieren, die erst entfernt oder aktualisiert werden können, wenn alle Abhängigkeiten aktualisiert wurden.
Interaktion mit der alten Engine
Für JavaScript-APIs, die Layoutinformationen lesen, sind weiterhin Legacy-Datenstrukturen zuständig. Daher müssen wir Daten in einer für die Legacy-Engine lesbaren Weise zurückschreiben. Dazu gehört die korrekte Aktualisierung der älteren mehrspaltigen Datenstrukturen wie LayoutMultiColumnFlowThread.
Erkennung und Verarbeitung von Legacy-Engine-Fallbacks
Wir mussten auf die alte Layout-Engine zurückgreifen, als sich darin Inhalte befanden, die von der LayoutNG-Blockfragmentierung noch nicht verarbeitet werden konnten. Zum Zeitpunkt der Veröffentlichung war die Blockfragmentierung von LayoutNG-Kernelementen ein Problem, einschließlich Flexbox, Raster, Tabellen und allem, was gedruckt wird. Dies war besonders schwierig, da wir ermitteln mussten, ob ein Legacy-Fallback erforderlich war, bevor Objekte im Layoutbaum erstellt wurden. Beispielsweise mussten wir feststellen, ob es einen mehrspaltigen Container-Vorgänger gab, bevor wir wussten, welche DOM-Knoten ein Formatierungskontext werden würden oder nicht. Es ist ein Henne-Ei-Problem, für das es keine perfekte Lösung gibt. Solange es sich aber nur um Falschalarme handelt (Fallback auf das alte Layout, wenn dies eigentlich nicht erforderlich ist), ist das in Ordnung, da alle Fehler in diesem Layoutverhalten bereits in Chromium vorhanden sind und nicht neu sind.
Vor dem Lackieren: Baumspaziergang
Die Vorab-Bearbeitung erfolgt nach dem Layout, aber vor dem Streichen. Die Hauptherausforderung besteht darin, dass wir weiterhin den Layout-Objektbaum durchgehen müssen, aber jetzt NG-Fragmente haben. Wie gehen wir damit um? Wir durchlaufen sowohl den Layout-Objekt- als auch den NG-Fragmentbaum gleichzeitig. Das ist ziemlich kompliziert, da die Kartierung zwischen den beiden Bäumen nicht so einfach ist.
Die Struktur des Layoutobjektbaums ähnelt zwar stark der des DOM-Baums, der Fragmentbaum ist jedoch eine Ausgabe des Layouts und keine Eingabe. Neben der tatsächlichen Darstellung der Auswirkungen jeglicher Fragmentierung, einschließlich Inline-Fragmentierung (Zeilenfragmente) und Blockfragmentierung (Spalten- oder Seitenfragmente), hat der Fragmentbaum auch eine direkte Eltern-Kind-Beziehung zwischen einem enthaltenden Block und den DOM-Nachkommen, die dieses Fragment als enthaltenden Block haben. Im Fragmentbaum ist beispielsweise ein Fragment, das von einem absolut positionierten Element generiert wird, ein direktes untergeordnetes Element des enthaltenden Blockfragments, auch wenn sich zwischen dem außerhalb des Flusses positionierten Nachkommen und dem enthaltenden Block andere Knoten in der Abstammungskette befinden.
Es kann noch komplizierter werden, wenn sich innerhalb der Fragmentierung ein Element befindet, das außerhalb des Flusses positioniert ist, da die Elemente außerhalb des Flusses dann direkte untergeordnete Elemente des Fragmentators werden (und nicht ein untergeordnetes Element dessen, was in CSS als enthaltender Block betrachtet wird). Dieses Problem musste gelöst werden, damit die neue Engine neben der alten Engine verwendet werden konnte. In Zukunft sollten wir diesen Code vereinfachen können, da LayoutNG alle modernen Layoutmodi flexibel unterstützt.
Probleme mit der alten Fragmentierungs-Engine
Die alte Engine, die in einer früheren Phase des Webs entwickelt wurde, kennt das Konzept der Fragmentierung nicht wirklich, auch wenn es sie damals technisch gab (um das Drucken zu unterstützen). Der Fragmentierungssupport wurde einfach oben angeschraubt (Druck) oder nachträglich eingebaut (mehrere Spalten).
Beim Layouten von fragmentierbaren Inhalten ordnet die alte Engine alles in einem hohen Streifen an, dessen Breite der Inline-Größe einer Spalte oder Seite entspricht und dessen Höhe so hoch ist, wie es für den Inhalt erforderlich ist. Dieser hohe Streifen wird nicht auf der Seite gerendert. Stellen Sie sich das als Rendering auf einer virtuellen Seite vor, die dann für die endgültige Anzeige neu angeordnet wird. Es ähnelt konzeptionell dem Ausdrucken eines ganzen Zeitungsartikels aus Papier in einer Spalte, den er dann in einem zweiten Schritt mit einer Schere in mehrere Teile zerlegt. (Früher haben einige Zeitungen tatsächlich ähnliche Techniken verwendet!)
Die alte Engine überwacht eine imaginäre Seiten- oder Spaltengrenze im Streifen. So kann er Inhalte, die nicht über die Begrenzung hinausgehen, auf die nächste Seite oder Spalte verschieben. Wenn beispielsweise nur die obere Hälfte einer Zeile auf die aktuelle Seite passt, wird ein „Paginierungs-Stützelement“ eingefügt, um sie an die Position zu schieben, die der Engine als Anfang der nächsten Seite erscheint. Der Großteil der eigentlichen Fragmentierung (das „Zuschneiden mit der Schere und Platzieren“) erfolgt dann nach dem Layout während des Vor- und des eigentlichen Zeichnens, indem der hohe Inhaltsstreifen in Seiten oder Spalten geteilt wird (durch Zuschneiden und Verschieben von Teilen). Dies machte einige Dinge im Grunde unmöglich, z. B. die Anwendung von Transformationen und relativer Positionierung nach der Fragmentierung (wie in der Spezifikation gefordert). Außerdem wird in der alten Engine zwar die Tabellenfragmentierung unterstützt, aber Flex- oder Rasterfragmentierung wird überhaupt nicht unterstützt.
Hier ist eine Abbildung, wie ein dreispaltiges Layout intern in der Legacy-Engine dargestellt wird, bevor wir eine Schere, die Platzierung und den Klebstoff verwenden (wir haben eine bestimmte Höhe, sodass nur vier Zeilen passen, aber unten ist etwas überschüssiger Platz):
Da die alte Layout-Engine Inhalte beim Layout nicht fragmentiert, gibt es viele seltsame Artefakte, z. B. eine falsche Anwendung relativer Positionierung und Transformationen sowie abgeschnittene Schatten an Säulenrändern.
Hier ein Beispiel mit text-shadow:
Die Legacy-Engine verarbeitet dies nicht gut:
Sehen Sie, wie der Textschatten der Zeile in der ersten Spalte abgeschnitten und stattdessen oben in der zweiten Spalte platziert wird? Das liegt daran, dass die alte Layout-Engine Fragmentierung nicht versteht.
Sie sollte so aussehen:
Als Nächstes machen wir es etwas komplizierter und verwenden Transformierungen und Schatten. Beachten Sie, dass bei der alten Engine ein falscher Zuschnitt und ein Spaltenüberlauf zu sehen sind. Das liegt daran, dass Transformationen gemäß Spezifikation als Post-Layout- und Post-Fragmentierungseffekt angewendet werden sollen. Bei LayoutNG-Fragmentierung funktionieren beide korrekt. Dadurch wird die Interoperabilität mit Firefox erhöht, das seit einiger Zeit gute Fragmentierung unterstützt und die meisten Tests in diesem Bereich auch dort bestanden werden.
Die alte Engine hat auch Probleme mit hohen monolithischen Inhalten. Inhalte sind monolithisch, wenn sie nicht in mehrere Fragmente aufgeteilt werden können. Elemente mit Überlauf-Scrollen sind monolithisch, da es für Nutzer nicht sinnvoll ist, in einem nicht rechteckigen Bereich zu scrollen. Linienfelder und Bilder sind weitere Beispiele für monolithische Inhalte. Beispiel:
Wenn der monolithische Inhalt zu hoch ist, um in eine Spalte zu passen, wird er von der alten Engine brutal zerstückelt. Das führt zu sehr „interessantem“ Verhalten beim Scrollen des scrollbaren Containers:
Anstatt dass der Text in die erste Spalte überläuft (wie bei der LayoutNG-Blockfragmentierung):
Die alte Engine unterstützt erzwungene Pausen. Mit <div style="break-before:page;">
wird beispielsweise vor dem DIV ein Seitenumbruch eingefügt. Es gibt jedoch nur begrenzte Unterstützung für die Suche nach optimalen nicht erzwungenen Umbrüchen. break-inside:avoid
und Waisen und Witwen werden unterstützt, aber es gibt keine Möglichkeit, Lücken zwischen Blöcken zu vermeiden, die beispielsweise über break-before:avoid
angefordert werden. Betrachten Sie dieses Beispiel:
Hier bietet das #multicol
-Element Platz für fünf Zeilen in jeder Spalte (da es 100 Pixel hoch ist und die Zeilenhöhe 20 Pixel beträgt), sodass alle #firstchild
-Elemente in die erste Spalte passen könnten. Bei seinem verwandten #secondchild
wird jedoch „break-before:avoid“ verwendet, was bedeutet, dass zwischen den Inhalten keine Pause erfolgen soll. Da der Wert von widows
2 ist, müssen wir zwei Zeilen von #firstchild
in die zweite Spalte einfügen, um alle Anfragen zur Vermeidung von Pausen zu berücksichtigen. Chromium ist die erste Browser-Engine, die diese Kombination von Funktionen vollständig unterstützt.
Funktionsweise der NG-Fragmentierung
Das NG-Layout-Engine legt das Dokument im Allgemeinen durch Tiefensuche im CSS-Boxbaum aus. Wenn alle Nachkommen eines Knotens angeordnet sind, kann das Layout dieses Knotens abgeschlossen werden, indem ein NGPhysicalFragment erstellt und zum übergeordneten Layoutalgorithmus zurückgekehrt wird. Dieser Algorithmus fügt das Fragment der Liste der untergeordneten Fragmente hinzu und generiert, sobald alle untergeordneten Fragmente fertig sind, ein Fragment für sich selbst mit allen untergeordneten Fragmenten. Mit dieser Methode wird eine Fragmentstruktur für das gesamte Dokument erstellt. Das ist jedoch eine Vereinfachung: Elemente, die außerhalb des Flusses positioniert sind, müssen beispielsweise von ihrer Position im DOM-Baum zum übergeordneten Block aufsteigen, bevor sie layoutet werden können. Der Einfachheit halber ignoriere ich diese erweiterten Details hier.
Zusammen mit dem CSS-Feld stellt LayoutNG einen Einschränkungsraum für einen Layout-Algorithmus bereit. So erhält der Algorithmus Informationen wie den verfügbaren Platz für das Layout, ob ein neuer Formatierungskontext festgelegt wurde und ob Zwischenabstände aufgrund des vorausgehenden Inhalts minimiert werden. Der Constraint-Bereich kennt auch die ausgelegte Blockgröße des Fragmentators und den aktuellen Blockoffset darin. Dies gibt an, wo eine Unterbrechung erfolgen soll.
Bei Blockfragmentierung muss das Layout der untergeordneten Elemente an einer Unterbrechung enden. Gründe für den Absturz sind beispielsweise nicht genügend Speicherplatz auf der Seite oder Spalte oder ein erzwungener Umbruch. Anschließend erstellen wir Fragmente für die besuchten Knoten und kehren bis zum Stamm des Fragmentierungsumfelds zurück (der Multicol-Container oder, im Fall des Druckens, der Dokumentstamm). Dann bereiten wir uns am Stamm des Fragmentierungskontexts auf einen neuen Fragmentator vor und steigen wieder in den Baum ein, wobei wir dort fortfahren, wo wir vor der Unterbrechung aufgehört haben.
Die entscheidende Datenstruktur, die es ermöglicht, das Layout nach einer Unterbrechung fortzusetzen, wird NGBlockBreakToken genannt. Sie enthält alle Informationen, die erforderlich sind, um das Layout im nächsten Fragmentainer korrekt fortzusetzen. Ein NGBlockBreakToken ist mit einem Knoten verknüpft und bildet einen NGBlockBreakToken-Baum, sodass jeder Knoten, der fortgesetzt werden muss, dargestellt wird. Ein NGBlockBreakToken wird an das NGPhysicalBoxFragment angehängt, das für Knoten generiert wurde, die eindringen. Die Pausentokens werden an die übergeordneten Elemente weitergegeben, wodurch eine Baumstruktur von Pausentokens gebildet wird. Wenn wir einen Abschnitt vor einem Knoten (nicht innerhalb) einfügen müssen, wird kein Fragment erstellt. Der übergeordnete Knoten muss jedoch trotzdem ein „break-before“-Abschnittstoken für den Knoten erstellen, damit wir mit der Layout-Erstellung beginnen können, wenn wir im nächsten Fragmentainer an derselben Position im Knotenbaum ankommen.
Pausen werden eingefügt, wenn entweder der Fragment-Container-Speicherplatz aufgebraucht ist (eine ungezwungene Pause) oder eine erzwungene Pause angefordert wird.
In der Spezifikation gibt es Regeln für optimale ungezwungene Umbrüche. Es ist nicht immer richtig, einen Umbruch genau dort einzufügen, wo der Platz aufgebraucht ist. Es gibt beispielsweise verschiedene CSS-Eigenschaften wie break-before
, die die Auswahl des Umbruchs beeinflussen.
Damit der Spezifikationsabschnitt für nicht erzwungene Unterbrechungen während des Layouts korrekt implementiert werden kann, müssen wir mögliche Haltepunkte im Auge behalten. Dieser Eintrag bedeutet, dass wir zurückgehen und den letzten gefundenen Haltepunkt verwenden können, wenn der Speicherplatz an einem Punkt zur Verfügung steht, an dem wir gegen Anfragen zur Pausenvermeidung verstoßen würden (z. B. break-before:avoid
oder orphans:7
). Jedem möglichen Haltepunkt wird eine Punktzahl zugewiesen. Diese reicht von "Machen Sie dies nur als letzte Option" bis "Perfekter Ort zum Umschalten", wobei einige Werte dazwischen liegen. Wenn ein Werbeunterbrechungs-Standort die Bewertung „perfekt“ erhält, bedeutet das, dass keine Werbeunterbrechungsregeln verletzt werden, wenn wir dort eine Unterbrechung einlegen. Wenn wir diese Bewertung genau an dem Punkt erhalten, an dem der Platz aufgebraucht ist, müssen wir nicht zurückgehen und nach einer besseren Stelle suchen. Wenn der Wert „letzte-Instanz“ ist, ist der Breakpoint nicht einmal gültig. Wir können ihn aber trotzdem verwenden, wenn wir nichts Besseres finden, um einen Fragmenter-Overflow zu vermeiden.
Gültige Breakpoints finden sich in der Regel nur zwischen Geschwistern (Zeilen- oder Blockelementen) und nicht beispielsweise zwischen einem übergeordneten Element und seinem ersten untergeordneten Element. Breakpoints der Klasse C sind eine Ausnahme, auf die wir hier aber nicht näher eingehen. Es gibt beispielsweise einen gültigen Bruchpunkt vor einem Block mit „break-before:avoid“, aber er liegt irgendwo zwischen „perfekt“ und „letzte Option“.
Während des Layouts wird der bisher beste gefundene Bruchpunkt in einer Struktur namens NGEarlyBreak verwaltet. Ein vorzeitiger Bruch ist ein möglicher Bruch vor oder innerhalb eines Blockknotens oder vor einer Zeile (entweder einer Blockcontainerzeile oder einer Flex-Zeile). Wir können eine Kette oder einen Pfad von NGEarlyBreak-Objekten bilden, falls sich der beste Unterbrechungspunkt irgendwo tief in einem Objekt befindet, an dem wir zuvor vorbeigegangen sind, als der Platz aufgebraucht war. Beispiel:
In diesem Fall ist der Speicherplatz kurz vor #second
vollständig belegt, aber mit „break-before:avoid“ ist „break-before:avoid“ vorhanden, wodurch die Position der Pause „Verstoß gegen die Werbeunterbrechungvermeidung“ lautet. An dieser Stelle haben wir eine NGEarlyBreak-Kette von „inside #outer
> inside #middle
> inside #inner
> before "line 3"' mit „perfect“. Daher sollten wir dort eine Unterbrechung einlegen. Daher müssen wir zurückgehen und das Layout von Anfang #outer aus neu ausführen (und dieses Mal die gefundene NGEarlyBreak übergeben), damit wir vor „Zeile 3“ in #inner einen Bruch einfügen können. (Wir unterbrechen vor Zeile 3, sodass die verbleibenden vier Zeilen im nächsten Fragmentierer landen, und um widows:4
zu berücksichtigen.)
Der Algorithmus ist so konzipiert, dass er immer am bestmöglichen Bruchpunkt – wie in der Spezifikation definiert – bricht. Dazu werden Regeln in der richtigen Reihenfolge verworfen, wenn nicht alle erfüllt werden können. Hinweis: Das Layout muss maximal einmal pro Fragmentierungsprozess neu erstellt werden. Zu Beginn des zweiten Layoutdurchgangs wurde die beste Textzeilen-Trennstelle bereits an die Layoutalgorithmen übergeben. Diese Textzeilen-Trennstelle wurde im ersten Layoutdurchgang ermittelt und in dieser Runde als Teil der Layoutausgabe bereitgestellt. Bei der zweiten Layout-Passage legen wir den Layout-Code erst dann aus, wenn der Platz aufgebraucht ist. Das sollte aber nicht passieren, da wir einen supergünstigen (na ja, so günstig wie möglich) Platz gefunden haben, um einen frühen Seitenumbruch einzufügen, um unnötige Verstöße gegen die Regeln für Seitenumbrüche zu vermeiden. Also legen wir uns auf diesen Punkt und machen eine Pause.
In diesem Zusammenhang müssen wir manchmal gegen einige der Anfragen zur Vermeidung von Pausen verstoßen, wenn dies dazu beiträgt, einen Fragmentierungsüberlauf zu vermeiden. Beispiel:
Hier ist direkt vor #second
kein Platz mehr, aber es hat „break-before:avoid“. Das wird genau wie im letzten Beispiel in „Verstoß gegen Pause vermeiden“ übersetzt. Wir haben auch eine NGEarlyBreak mit „verstoßende Waisen und Witwen“ (in #first
> vor „Zeile 2“), die immer noch nicht perfekt ist, aber besser als „Verstoß gegen die Werbeunterbrechung vermeiden“. Wir unterbrechen also vor Zeile 2 und verstoßen gegen den Antrag für Waisen / Witwen. In Paragraf 4.4 der Spezifikation wird darauf eingegangen. „Unforced Breaks“, wo definiert wird, welche Trennregeln zuerst ignoriert werden, wenn nicht genügend Umbrüche vorhanden sind, um einen Fragmentierungsüberlauf zu vermeiden.
Fazit
Das funktionale Ziel des LayoutNG-Blockfragmentierungsprojekts bestand darin, eine LayoutNG-Architektur zu implementieren, die alles unterstützt, was die alte Engine unterstützt, und abgesehen von Fehlerkorrekturen so wenig wie möglich. Die wichtigste Ausnahme ist die bessere Unterstützung zur Vermeidung von Unterbrechungen (z. B. break-before:avoid
), da dies ein zentraler Bestandteil der Fragmentierungs-Engine ist und sie von Anfang an darin enthalten sein musste, da ein späteres Hinzufügen eine weitere Umformulierung bedeuten würde.
Nachdem die Blockfragmentierung in LayoutNG abgeschlossen ist, können wir mit der Implementierung neuer Funktionen beginnen, z. B. die Unterstützung gemischter Seitengrößen beim Drucken, @page
Rahmen für Ränder beim Drucken, box-decoration-break:clone
und mehr. Wie bei LayoutNG im Allgemeinen gehen wir davon aus, dass die Fehlerrate und der Wartungsaufwand des neuen Systems im Laufe der Zeit deutlich sinken werden.
Danksagungen
- Una Kravets für den schönen "handgefertigten Screenshot".
- Chris Harrelson für die Korrekturlesen, das Feedback und die Vorschläge.
- Philip Jägenstedt für Feedback und Vorschläge.
- Rachel Andrew für die Bearbeitung und die erste mehrspaltige Beispielgrafik.