Optimizing SVG Patterns to Their Smallest Size

Avatar of Bence Szabó
Bence Szabó am

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

I recently created a brick wall pattern as part of my #PetitePatterns series, a challenge where I create organic-looking patterns or textures in SVG within 560 bytes (or approximately the size of two tweets). To fit this constraint, I have gone through a journey that has taught me some radical ways of optimizing SVG patterns so that they contain as little code as possible without affecting the overall image quality.

I want to walk you through the process and show you how we can take an SVG pattern that starts at 197 bytes all the way down to a mere 44 bytes — a whopping 77.7% reduction!

Das SVG-Muster

Dies ist ein sogenanntes "Läuferverband"-Ziegelmuster. Es ist das gängigste Ziegelmuster und eines, das Sie sicher schon einmal gesehen haben: Jede Ziegelreihe ist um die halbe Ziegellänge versetzt, wodurch ein sich wiederholendes, gestaffeltes Muster entsteht. Die Anordnung ist ziemlich einfach, wodurch sich das <pattern>-Element von SVG perfekt eignet, um es im Code zu reproduzieren.

Das SVG-Element <pattern> verwendet ein vordefiniertes grafisches Objekt, das an festen Intervallen entlang der horizontalen und vertikalen Achsen wiederholt (oder "gekachelt") werden kann. Im Wesentlichen definieren wir eine rechteckige Kachel und diese wird wiederholt, um den Füllbereich zu malen.

Legen wir zunächst die Abmessungen eines Ziegels und den Abstand zwischen den Ziegeln fest. Der Einfachheit halber verwenden wir einfache, runde Zahlen: eine Breite von 100 und eine Höhe von 30 für den Ziegel und 10 für die horizontalen und vertikalen Abstände dazwischen.

Showing a highlighted portion of a brick wall pattern, which is the example we are using for optimizing SVG patterns.

Als Nächstes müssen wir unsere "Basis"-Kachel identifizieren. Und mit "Kachel" meine ich Musterkacheln, nicht physische Fliesen, nicht zu verwechseln mit den Ziegeln. Nehmen wir den hervorgehobenen Teil des obigen Bildes als unsere Musterkachel: zwei ganze Ziegel in der ersten Reihe und ein ganzer Ziegel, der in der zweiten Reihe zwischen zwei halben Ziegeln eingeklemmt ist. Beachten Sie, wie und wo die Abstände einbezogen sind, denn diese müssen in die wiederholte Musterkachel einbezogen werden.

Bei der Verwendung von <pattern> müssen wir die width und height des Musters definieren, die der Breite und Höhe der Basis-Kachel entsprechen. Um die Abmessungen zu erhalten, benötigen wir ein wenig Mathematik.

Tile Width  = 2(Brick Width) + 2(Gap) = 2(100) + 2(10) = 220
Tile Height = 2(Bright Height) + 2(Gap) = 2(30) + 2(10) = 80

Okay, also ist unsere Musterkachel 220✕80 groß. Wir müssen auch das Attribut patternUnits setzen, wobei der Wert userSpaceOnUse im Wesentlichen Pixel bedeutet. Schließlich ist das Hinzufügen einer id zum Muster notwendig, damit es referenziert werden kann, wenn wir ein anderes Element damit malen.

<pattern id="p" width="220" height="80" patternUnits="userSpaceOnUse">
  <!-- pattern content here -->
</pattern>

Nachdem wir nun die Abmessungen der Kachel festgelegt haben, besteht die Herausforderung darin, den Code für die Kachel so zu erstellen, dass die Grafik mit der geringstmöglichen Bytezahl gerendert wird. Das ist das, was wir am Ende erreichen wollen.

Die Ziegel (in Schwarz) und die Abstände (in Weiß) des finalen Läuferverbandsmusters

Anfängliches Markup (197 Bytes)

Der einfachste und deklarativste Ansatz, um dieses Muster zu reproduzieren, der mir einfällt, ist das Zeichnen von fünf Rechtecken. Standardmäßig ist die fill eines SVG-Elements schwarz und die stroke transparent. Das funktioniert gut für die Optimierung von SVG-Mustern, da wir diese nicht explizit im Code deklarieren müssen.

Jede Zeile im folgenden Code definiert ein Rechteck. Die width und height sind immer gesetzt, und die x- und y-Positionen werden nur gesetzt, wenn ein Rechteck vom 0-Punkt versetzt ist.

<rect width="100" height="30"/>
<rect x="110" width="100" height="30"/>
<rect y="40" width="45" height="30"/>
<rect x="55" y="40" width="100" height="30"/>
<rect x="165" y="40" width="55" height="30"/>

Die obere Reihe der Kachel enthielt zwei Ziegel voller Breite, der zweite Ziegel ist bei x="110" positioniert, was einen Abstand von 10 Pixeln vor dem Ziegel ermöglicht. Ähnlich gibt es 10 Pixel Abstand danach, da der Ziegel bei 210 Pixeln (110 + 100 = 210) auf der horizontalen Achse endet, obwohl die <pattern>-Breite 220 Pixel beträgt. Wir brauchen diesen kleinen zusätzlichen Platz; andernfalls würde der zweite Ziegel mit dem ersten Ziegel in der benachbarten Kachel verschmelzen.

Die Ziegel in der zweiten (unteren) Reihe sind versetzt, sodass die Reihe zwei halbe Ziegel und einen ganzen Ziegel enthält. In diesem Fall möchten wir, dass die halben Ziegel verschmelzen, sodass am Anfang oder Ende kein Abstand vorhanden ist, was ein nahtloses Zusammenfließen mit den Ziegeln in angrenzenden Musterkacheln ermöglicht. Beim Versetzen dieser Ziegel müssen wir auch halbe Abstände berücksichtigen, daher sind die x-Werte 55 bzw. 165.

Element-Wiederverwendung, (-43B, 154B insgesamt)

Es erscheint ineffizient, jeden Ziegel so explizit zu definieren. Gibt es keine Möglichkeit, SVG-Muster durch Wiederverwendung der Formen zu optimieren?

Ich glaube nicht, dass es weithin bekannt ist, dass SVG ein <use>-Element hat. Sie können damit ein anderes Element referenzieren und dieses referenzierte Element überall rendern, wo <use> verwendet wird. Das spart einige Bytes, da wir die Breiten und Höhen jedes Ziegels weglassen können, außer dem ersten.

Das gesagt, <use> kommt mit einem kleinen Preis. Das heißt, wir müssen eine id für das wiederzuverwendende Element hinzufügen.

<rect id="b" width="100" height="30"/>
<use href="#b" x="110"/>
<use href="#b" x="-55" y="40"/>
<use href="#b" x="55" y="40"/>
<use href="#b" x="165" y="40"/>

Die kürzeste mögliche id ist ein Zeichen, daher habe ich "b" für Ziegel gewählt. Das <use>-Element kann ähnlich wie <rect> positioniert werden, mit den Attributen x und y als Offsets. Da jeder Ziegel volle Breite hat, da wir zu <use> gewechselt haben (denken Sie daran, dass wir die Ziegel in der zweiten Reihe des Musterkachels explizit halbiert haben), müssen wir in der zweiten Reihe einen negativen x-Wert verwenden und dann sicherstellen, dass der letzte Ziegel aus der Kachel überläuft, um eine nahtlose Verbindung zwischen den Ziegeln zu gewährleisten. Das ist aber in Ordnung, da alles, was außerhalb der Musterkachel liegt, automatisch abgeschnitten wird.

Können Sie wiederholende Zeichenketten erkennen, die effizienter geschrieben werden können? Lassen Sie uns das als Nächstes angehen.

Umschreiben zu Pfad (-54B, 100B insgesamt)

<path> ist wahrscheinlich das mächtigste Element in SVG. Sie können fast jede Form mit "Befehlen" in seinem d-Attribut zeichnen. Es gibt 20 verfügbare Befehle, aber wir brauchen nur die einfachsten für Rechtecke.

Hier ist, wo ich damit gelandet bin.

<path d="M0 0h100v30h-100z
         M110 0h100v30h-100
         M0 40h45v30h-45z
         M55 40h100v30h-100z
         M165 40h55v30h-55z"/>

Ich weiß, super seltsame Zahlen und Buchstaben! Sie haben alle eine Bedeutung, natürlich. Hier ist, was in diesem speziellen Fall passiert.

  • M{x} {y}: Bewegt sich zu einem Punkt basierend auf Koordinaten.
  • z: Schließt das aktuelle Segment.
  • h{x}: Zeichnet eine horizontale Linie vom aktuellen Punkt aus, mit der Länge von x in der Richtung, die durch das Vorzeichen von x definiert wird. Kleinbuchstabe x bedeutet relative Koordinate.
  • v{y}: Zeichnet eine vertikale Linie vom aktuellen Punkt aus, mit der Länge von y in der Richtung, die durch das Vorzeichen von y definiert wird. Kleinbuchstabe y bedeutet relative Koordinate.

Dieses Markup ist viel kürzer als das vorherige (Zeilenumbrüche und Einrückungen dienen nur der Lesbarkeit). Und hey, wir haben die Hälfte der ursprünglichen Größe eingespart und sind bei 100 Bytes angekommen. Dennoch habe ich das Gefühl, dass das noch kleiner sein könnte...

Kachel-Revision (-38B, 62B insgesamt)

Hat unsere Musterkachel nicht wiederholende Teile? Es ist klar, dass in der ersten Reihe ein ganzer Ziegel wiederholt wird, aber was ist mit der zweiten Reihe? Es ist etwas schwieriger zu erkennen, aber wenn wir den mittleren Ziegel halbieren, wird es offensichtlich.

The left half preceding the red line is the same as the right side.

Nun, der mittlere Ziegel ist nicht exakt halbiert. Es gibt einen leichten Versatz, da wir auch den Abstand berücksichtigen müssen. Jedenfalls haben wir eine einfachere Basis-Musterkachel gefunden, was weniger Bytes bedeutet! Das bedeutet auch, dass wir die width unseres <pattern>-Elements von 220 auf 110 halbieren müssen.

<pattern id="p" width="110" height="80" patternUnits="userSpaceOnUse">
  <!-- pattern content here -->
</pattern>

Lassen Sie uns nun sehen, wie die vereinfachte Kachel mit <path> gezeichnet wird.

<path d="M0 0h100v30h-100z
         M0 40h45v30h-45z
         M55 40h55v30h-55z"/>

Die Größe ist auf 62 Bytes reduziert, was bereits weniger als ein Drittel der ursprünglichen Größe ist! Aber warum aufhören, wenn es noch mehr gibt, was wir tun können!

Verkürzung von Pfadbefehlen (-9B, 53B insgesamt)

Es lohnt sich, sich etwas tiefer mit dem <path>-Element zu beschäftigen, da es weitere Hinweise zur Optimierung von SVG-Mustern liefert. Ein Missverständnis, das ich bei der Arbeit mit <path> hatte, betrifft die Funktionsweise des fill-Attributs. Nachdem ich als Kind viel mit MS Paint gespielt hatte, lernte ich, dass jede Form, die ich mit einer Volltonfarbe füllen wollte, geschlossen sein musste, d.h. keine offenen Punkte haben durfte. Andernfalls würde die Farbe aus der Form austreten und sich über alles verteilen.

In SVG ist dies jedoch nicht der Fall. Lassen Sie mich die Spezifikation selbst zitieren.

Die Fülloperation füllt offene Unterpfade, indem sie die Fülloperation so ausführt, als wäre ein zusätzlicher "closepath"-Befehl zum Pfad hinzugefügt worden, um den letzten Punkt des Unterpfads mit dem ersten Punkt des Unterpfads zu verbinden.

Das bedeutet, dass wir die Close-Path-Befehle (z) weglassen können, da die Unterpfade beim Füllen automatisch als geschlossen betrachtet werden.

Eine weitere nützliche Sache, die man über Pfadbefehle wissen sollte, ist, dass sie in Groß- und Kleinschreibungsvarianten vorkommen. Kleinbuchstaben bedeuten, dass relative Koordinaten verwendet werden; Großbuchstaben bedeuten, dass stattdessen absolute Koordinaten verwendet werden.

Bei den Befehlen H und V ist es etwas kniffliger, da sie nur eine Koordinate enthalten. Hier sind, wie ich diese beiden Befehle beschreiben würde:

  • H{x}: Zeichnet eine horizontale Linie vom aktuellen Punkt zur Koordinate x.
  • V{y}: Zeichnet eine vertikale Linie vom aktuellen Punkt zur Koordinate y.

Wenn wir den ersten Ziegel in der Musterkachel zeichnen, beginnen wir bei den Koordinaten (0,0). Dann zeichnen wir eine horizontale Linie zu (100,0) und eine vertikale Linie zu (100,30), und schließlich eine horizontale Linie zu (0,30). Wir haben den Befehl h-100 in der letzten Zeile verwendet, aber das ist äquivalent zu H0, was zwei Bytes statt fünf sind. Wir können zwei ähnliche Vorkommen ersetzen und den Code unseres <path> auf Folgendes reduzieren:

<path d="M0 0h100v30H0
         M0 40h45v30H0
         M55 40h55v30H55"/>

Weitere 9 Bytes eingespart – wie viel kleiner können wir werden?

Brückenbildung (-5B, 48B insgesamt)

Die längsten Befehle, die uns auf dem Weg zu einem vollständig optimierten SVG-Muster im Weg stehen, sind die "move to"-Befehle, die jeweils 4, 5 und 6 Bytes beanspruchen. Eine Einschränkung, die wir haben, ist, dass

Ein Pfaddatensegment (falls vorhanden) muss mit einem "moveto"-Befehl beginnen.

Aber das ist in Ordnung. Der erste ist sowieso der kürzeste. Wenn wir die Reihen tauschen, können wir eine Pfaddefinition erstellen, bei der wir uns nur horizontal oder vertikal zwischen den Ziegeln bewegen müssen. Was wäre, wenn wir dort die Befehle h und v anstelle von M verwenden könnten?

Der Pfad beginnt am roten Punkt in der oberen linken Ecke. Rot sind die Pfadbefehle, die mit Pfeilen unterstützt werden, schwarz sind die Koordinaten, auf die die Pfeile zeigen.

Das obige Diagramm zeigt, wie die drei Formen mit einem einzigen Pfad gezeichnet werden können. Beachten Sie, dass wir die Tatsache nutzen, dass die fill-Operation den offenen Teil zwischen (110,0) und (0,0) automatisch schließt. Mit dieser Umordnung haben wir auch den Abstand links vom Ziegel voller Breite in der zweiten Reihe platziert. So sieht der Code aus, immer noch in eine Kachel pro Zeile aufgeteilt.

<path d="M0 0v30h50V0
         h10v30h50
         v10H10v30h100V0"/>

Sicherlich haben wir jetzt die absolut kleinste Lösung gefunden, da wir bei 48 Bytes angelangt sind, richtig?! Nun…

Zifferntrimming (-4B, 44B insgesamt)

Wenn Sie bei den Abmessungen etwas flexibel sein können, gibt es noch eine weitere kleine Möglichkeit, SVG-Muster zu optimieren. Wir haben mit einer Ziegelbreite von 100 Pixeln gearbeitet, das sind aber drei Bytes. Wenn wir sie auf 90 ändern, spart das ein Byte jedes Mal, wenn wir sie schreiben müssen. Ebenso haben wir einen Abstand von 10 Pixeln verwendet – aber wenn wir ihn stattdessen auf 8 ändern, sparen wir bei jeder dieser Vorkommnisse ein Byte.

<path d="M0 0v30h45V0
         h8v30h45
         v8H8v30h90V0"/>

Natürlich bedeutet das auch, dass wir die Musterabmessungen entsprechend anpassen müssen. Hier ist der endgültige optimierte SVG-Muster-Code.

<pattern id="p" width="98" height="76" patternUnits="userSpaceOnUse">
  <path d="M0 0v30h45V0h8v30h45v8H8v30h90V0"/>
</pattern>

Die zweite Zeile im obigen Snippet – ohne die Einrückungen – ist **44 Bytes** lang. Wir sind in sechs Iterationen von 197 Bytes hierher gekommen. Das ist eine satte **77,7% Größenreduzierung**!

Ich frage mich jedoch… ist das wirklich die kleinste mögliche Größe? Haben wir alle möglichen Wege zur Optimierung von SVG-Mustern betrachtet?

Ich lade Sie ein, diesen Code weiter zu minimieren oder sogar alternative Methoden zur Optimierung von SVG-Mustern auszuprobieren. Ich würde gerne sehen, ob wir mit der Weisheit der Menge das wahre globale Minimum finden könnten!

Mehr über die Erstellung und Optimierung von SVG-Mustern

Wenn Sie mehr über die Erstellung und Optimierung von SVG-Mustern erfahren möchten, lesen Sie meinen Artikel über die Erstellung von Mustern mit SVG-Filtern. Oder wenn Sie eine Galerie mit über 60 Mustern sehen möchten, können Sie die PetitePatterns CodePen Collection ansehen. Schließlich sind Sie herzlich eingeladen, sich meine Tutorials auf YouTube anzusehen, die Ihnen helfen, tiefer in SVG-Muster einzudringen.