SVG/Tutorials/Strukturieren und Recyceln mit symbol und use
SVG-Grafiken bestehen aus vielen Objekten – die sich aus einer oder mehreren Grundformen, Pfaden oder Text zusammensetzen. Damit komplexe Grafiken übersichtlich bleiben, bietet SVG leistungsfähige Werkzeuge zur Strukturierung und Wiederverwendung von Elementen.
Mit <symbol> lassen sich wiederverwendbare grafische Bausteine definieren. Sie erscheinen zunächst nicht direkt sichtbar, sondern werden nur als Vorlage gespeichert.
Über <use> können diese Bausteine anschließend beliebig oft aufgerufen werden – in unterschiedlichen Größen, Farben oder Positionen. Das spart nicht nur Code, sondern verbessert auch die Wartbarkeit und lädt Grafiken schneller.
Ein Verständnis für diese Elemente ist der Schlüssel zu sauberem, modular aufgebautem SVG.
Inhaltsverzeichnis
Grafikobjekte duplizieren
Es hat sich herumgesprochen, dass man SVG-Icons im Editor optimieren kann, damit sie leichter mit CSS zu stylen und zu animieren sind.
Größere SVGs werden meist in Grafikprogrammen wie Inkscape oder Illustrator erstellt, in denen Grafik-Objekte wieder und wieder kopiert werden. Solchermaßen erzeugte SVGs sind zwar immer noch leichtgewichtiger als Rastergrafiken, leiden aber unter Code-Duplikaten und lassen sich nur schwierig weiterverarbeiten.
Aufrufen mit use
Mit dem <use>-Element kannst du ein vorher festgelegtes Objekt wie eine einzelne Grundform oder einen Text, eine Gruppierung mit g oder symbol über die ID beliebig oft aufrufen. So sparst du Speicherplatz und hältst deine SVG-Grafiken übersichtlich.
<g id="tree">
<rect class="stub" x="290" y="175" height="20" width="10"/>
<path class="tree" d="m 295,100 c -7,14 -18,61 -53,89 c 36,-14 72,-14 106,0 c -27,-14 -43,-61 -53,-89"/>
<path class="tree" d="m 295,55 c -7,14 -18,61 -53,89 c 36,-14 72,-14 106,0 c -27,-14 -43,-61 -53,-89"/>
<path class="tree" d="m 295,10 c -7,14 -18,61 -53,89 c 36,-14 72,-14 106,0 c -27,-14 -43,-61 -53,-89"/>
</g>
<use href="#tree" x="120" y="33"/>
<use href="#tree" x="-120" y="33"/>
Unser Baum beinhaltet 3 Pfade und ein rect in einem g mit einer eindeutigen id.
Die folgenden use-Elemente referenzieren den Baum über diese id.
transform="translate(x y)" angegeben (falls weitere Transformationen angegeben sind, wird die hier genannte zuerst ausgeführt, also so als ob sie am Ende des Attributwertes stände).Diese Vorgehensweise spart viel Markup, hat jedoch einen Nachteil:
Das Aussehen und Styling des use-Elements kann zwar mit allen Präsentationsattributen formatiert werden; die Farbfüllungen und Randlinien können nur im referenzierten Original, nicht im Duplikat gestaltet werden.
Deshalb ist es oft besser, solche Bausteine nicht direkt im SVG zu zeichnen, sondern erst im Definitionsabschnitt zu notieren, aber noch nicht zu rendern.
Bausteine mit symbol
Das <symbol> wird verwendet, um grafische Vorlagenobjekte zu definieren, die dann durch ein <use>-Element aufgerufen und platziert werden können. Diese Objekte werden wie Objekte im Definitionsabschnitt nicht gerendert, können mithilfe des use-Elements aber beliebig oft aufgerufen werden.
<defs>
<symbol id="branch">
<path class="branch" d="m 55,10 c -7,14 -18,61 -53,89 c 36,-14 72,-14 106,0 c -27,-14 -43,-61 -53,-89"/>
</symbol>
<symbol id="stub">
<rect class="stub" x="50" y="175" height="20" width="10"/>
</symbol>
<symbol id="tree">
<desc>Christmas Tree with 3 branches and stummp</desc>
<use href="#stub" />
<use href="#branch" x="0" y="90" />
<use href="#branch" x="0" y="50" />
<use href="#branch" x="0" y="10" />
</symbol>
</defs>
<use href="#tree" x="30" y="33"/>
Im oberen Beispiel bestand der Baum aus drei identischen Pfaden, die nun als <symbol> mit der id branch definiert werden. Auch der Baumstumpf wird in einem symbol #stub notiert.
Diese Bausteine werden in der #tree-Gruppierung referenziert. Es enthält eine Beschreibung mit desc, den Baumstumpf und die einzelnen Zweige. Diese werden dabei über das y-Attribut vertikal verschoben.
Dies bringt viele Vorteile mit sich:
- Anders als das g-Element kann man beim symbol-Element folgende Attribute setzen:
viewBox: neuen Viewport festlegenpreserveAspectRatio: Seitenverhältnis festlegen
- So können beliebige Grafiken passend skaliert eingebunden werden.
- Der Code wird übersichtlicher
- die Wiederverwendbarkeit führt bei häufiger Verwendung zu geringeren Datei-Größen
- durch die Verwendung von Metadaten wie title und desc wird der Code zugänglicher.
Styling des use-Elements
Jetzt möchte ich die Bäume unterschiedlich färben und stoße auf Schwierigkeiten. Wenn ich die Füllfarbe für #branch nicht setze, färbt eine Angabe für fill diese Bausteine in der gewünschten Farbe:
<defs>
<style type="text/css">
.branch {
stroke: darkgreen;
}
.new {
fill: gold;
}
</style>
</defs>
<use href="#tree" x="30" y="33" style="color:lime;"/>
<use href="#tree" x="150" y="0" style="color:seagreen;"/>
<use href="#tree-big" x="260" y="60" style="color:green;"/>
<use href="#tree" class="new" x="380" y="33"/>
Die einzelnen use-Elemente werden durch Klassen oder inline-Styles entsprechend gefärbt.
Wenn du das <use>-Element über eine Klasse oder eine strukturelle Pseudoklasse wie nth-of-type selektierst, kannst du, falls diese Eigenschaften vorher nicht deklariert wurden, Randfarbe, -stärke und Füllung des <use>-Elements formatieren. Dies gilt jedoch nicht für die in der Vorlage verwendeten Kind-Elemente.
Das funktioniert natürlich nicht; das use-Element benötigt dann eine eigene, unverwechselbare id:
<use href="#tree" id="einmalig" />use im Shadow-DOM
Information: Shadow DOM
Komplexe HTML-Elemente wie z. B. Slider oder das details-Element sind aus mehreren mehreren „versteckten“ Elementen zusammengebaut.Diese Elemente im Shadow DOM können im Allgemeinen weder mit JavaScript angesprochen noch mit CSS formatiert werden.
MIt use referenzierte Elemente können zwar aus beliebig vielen SVG-Elementen bestehen, werden auf dem Bildschirm (und im DOM der Seite) als ein Element dargestellt. Bei einem Blick in den Seiteninspektor sieht man, dass die einzelnen Teil-Elemente im #shadow-root-Knoten des use-Elements gekapselt sind. Ein direkter Zugriff auf diese Kindelemente ist nicht möglich.
Styling mit custom properties
Information: Custom properties
Mit custom properties (benutzerdefinierten Eigenschaften) kann man an beliebigen Elementen Werte hinterlegen. Diese werden mit vererbt und können mittelsvar() genutzt werden.Einen Ausweg bietet die Verwendung von Custom properties. Diese werden in die von <use> erstellte Kopie hineinvererbt und können deshalb von den Kindelemente der symbol-Vorlage benutzt werden, um beispielsweise eine Farbe zu setzen.
svg {
--akzentfarbe: green;
}
path {
fill: var(--akzentfarbe);
}
Im Beispiel wird eine Farbe festgelegt. Der Wert einer benutzerdefinierten Eigenschaft wird mit einem frei gewählten Namen festgelegt. Dieser beginnt mit zwei Minuszeichen.
Anschließend kann diese Variable im gesamtem Dokument mit der Variablenfunktion var() wieder aufgerufen werden.
<style type="text/css">
.branch:nth-of-type(1) {
stroke: var(--base-dark-1);
stroke-width: 0.5;
}
.stub {
fill: var(--stub-color);
stroke: oklch(from var(--stub-color) calc(l - 0.25) c h);
stroke-width: 0.5;
}
:root {
--base: green;
--base-light-1: oklch(from var(--base) calc(l + 0.1) c h);
--base-light-2: oklch(from var(--base) calc(l + 0.2 ) c h);
--base-dark-1: oklch(from var(--base) calc(l - 0.15) c h);
--stub-color: brown;
}
</style>
...
<symbol id="tree">
<desc>Christmas Tree with 3 branches and stummp</desc>
<use href="#stub" />
<use href="#branch" x="0" y="90" style="fill: var(--base);" />
<use href="#branch" x="0" y="50" style="fill: var(--base-light-1);" />
<use href="#branch" x="0" y="10" style="fill: var(--base-light-2);" />
</symbol>
Die einzelnen Zweige (#branch) erhalten nun jeweils eine Füllfarbe im use-Element. Da sie nicht im DOM stehen, können wir sie nicht über .branch:nth-of-type(2) { fill: seagreen; } ansprechen.
Die Werte für die Füllfarbe wird im :root anhand von Custom Propertys festgelegt. Dabei wird eine Grundfarbe --base definiert und aus dieser weitere Farbschattierungen gemischt.
Rundes muss rollen!
Das S in SVG bedeutet ja, dass man Grafik-Objekte beliebig skalieren kann.
Wenn man ein Ball oder ein Rad um sich selbst rotieren lassen will, muss man neben der Drehung auch den richtigen Drehpunkt finden. Einfacher ist es, wenn das Grafik-Objekt direkt um den Ursprung zeichnet:
<symbol id="ball"
x="-30" y="-30"
width="60" height="60"
viewBox="-30 -30 60 60">
<circle class="ball" cx="0" cy="0" r="28" />
</symbol>
<use href="#ball" id="ball-motion" width="25" height="25" />
Das symbol-Element enthält ein viewBox-Attribut, das um den Urspung zentriert ist (viewBox="-30 -30 60 60"). Zusätzlich erhält es die gleichen Werte als Attribute(x="-30" y="-30" height="60" width="60"). Dadurch erfolgt keine Skalierung (bzw. Faktor 1) und das Symbol wird innerhalb der Viewbox richtig positioniert. Ohne Positionierung sind nur positive Koordinaten möglich.
Im use-Element wird das symbol referenziert und mit zusätzlichen width- und height-Attributen versehen.
Anwendungsbeispiele
Osterwiese
<defs>
<symbol id="ei">
<path d="M1,90 a 60,60 0 0 0 120,0 a 60,90 0 1 0 -120,0" stroke="black" stroke-width="1"/>
</symbol>
<symbol id="tulpe" stroke="black" stroke-width="1">
<path d="M40,60 c0,0 -5,-20 12,-30 c10,10 15,20 15,75 h-30" />
<path d="M55,80 c5,-20 20,-50 33,-50 c30,140 -93,110 -73,8 c20,0 55,55 47,86 " />
</symbol>
<symbol id="stengel" stroke="black" stroke-width="1" fill="url(#gruen)">
<path d="M55,125 c10,10 16,60 15,80 h8 c0,-10 0,-60 -12,-80" />
<path d="M50,265 c0,-30 6,-60 60,-110 c-40,50 6,90 -60,110 c-40,-50 26,-90 -45,-150 c70,30 50,100
55,100" />
</symbol>
<radialGradient id="rot" cx="88%" cy="45%">
<stop offset="0%" stop-color="silver" />
<stop offset="100%" stop-color="#c32e04" />
</radialGradient>
<radialGradient id="gruen" cx="88%" cy="45%">
<stop offset="0%" stop-color="silver" />
<stop offset="100%" stop-color="#5a9900" />
</radialGradient>
<radialGradient id="gelb" cx="88%" cy="45%">
<stop offset="0%" stop-color="silver" />
<stop offset="100%" stop-color="#dfac20" />
</radialGradient>
<radialGradient id="blau" cx="88%" cy="45%">
<stop offset="0%" stop-color="silver" />
<stop offset="100%" stop-color="#3983ab" />
</radialGradient>
<linearGradient id="hintergrund" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="white" />
<stop offset="10%" stop-color="#e6f2f7" />
<stop offset="50%" stop-color="#5c82d9" />
<stop offset="50%" stop-color="#8db243 " />
<stop offset="100%" stop-color="#ebf5d7" />
</linearGradient>
</defs>
<rect x="0" y="0" width="880" height="500" fill="url(#hintergrund)" />
<g fill="url(#gelb)" transform="translate(0,0)">
<use href="#tulpe" />
<use href="#stengel" />
</g>
<g fill="url(#rot)" transform="translate(200,0) scale(-1,1)" >
<use href="#tulpe" />
<use href="#stengel" />
</g>
<g fill="url(#blau)" transform="translate(620,30) rotate(-20)">
<use href="#tulpe" />
<use href="#stengel" />
</g>
<g fill="url(#gelb)" transform="translate(850,10) scale(-1,1)" >
<use href="#tulpe" />
<use href="#stengel" />
</g>
<use fill="url(#rot)" href="#ei" transform="translate(70,220) rotate(-10,50,30) scale(.5,.5)" />
<use fill="url(#gelb)" href="#ei" transform="translate(30,250) scale(.5,.5)" />
<use fill="url(#blau)" href="#ei" transform="translate(100,250) rotate(30,50,50) scale(.5,.5)" />
<g transform="translate(600,360) scale(.5,.5)">
<use fill="url(#gelb)" href="#ei" transform="translate(0,0)" />
<use fill="url(#blau)" href="#ei" transform="translate(100,50) rotate(30,50,50)" />
<use fill="url(#rot)" href="#ei" transform="translate(-100,50) rotate(-30,50,30)" />
<use fill="url(#gruen)" href="#ei" transform="translate(0,100) rotate(-10)" />
</g>
Im Definitionsabschnitt werden mehrere symbol-Elemente definiert:
- ein ovales Ei
- eine Tulpenblüte ohne
fill-Attribut - Stengel und Blätter mit grüner Füllung
Anschließend werden diese Objekte mit use aufgerufen. Tulpe und Stengel sind in einem g-Element, das durch das transform-Attribut an die richtige Stelle verschoben wurde. Die mit use aufgerufenen Objekte erhalten eine jeweils andere Füllfarbe und werden bei Bedarf noch einmal transformiert.
Diese Osterwiese wurde 2015 als Ostergruß erstellt und dient dem Osterspiel 2024 als Hintergrund.
Eine Burg
Auf einigen Seiten ist eine Burg eingebunden. Hier kannst du sie noch einmal als Beispiel sehen:
<defs>
<style type="text/css">
<![CDATA[
#turm {
stroke: black;
stroke-width: 1;
fill:url(#mauerwerk);
}
#mauerwerk,#holz,#banner {
stroke: black;
stroke-width: 1;
}
#mauer {
fill:url(#mauerwerk);
}]]></style>
<pattern id="mauerwerk" x="0" y="0" width="48" height="19" patternUnits="userSpaceOnUse">
<rect width="48" height="20" fill="#FFF8DC" stroke-width="0"/>
<path d="M0,1 h48"/>
<path d="M0,10 h48"/>
<path d="M12,1 v9"/>
<path d="M36,1 v9"/>
<path d="M1,10 v9"/>
<path d="M25,10 v9"/>
</pattern>
<pattern id="holz" x="0" y="0" width="5" height="17" patternUnits="userSpaceOnUse">
<rect width="5" height="20" fill="#9c4822" stroke-width="0"/>
<path d="M0,0 v20"/>
</pattern>
<linearGradient id="hintergrund" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="white" />
<stop offset="10%" stop-color="#e6f2f7" />
<stop offset="100%" stop-color="#5c82d9" />
</linearGradient>
<radialGradient id="gelb" cx="50%" cy="50%">
<stop offset="0%" stop-color="yellow" />
<stop offset="100%" stop-color="#dfac20" />
</radialGradient>
<symbol id="turm">
<desc>Turm der Burg</desc>
<path d="M1,1 h8 v9 h8 v-9 h8 v9h8 v-9 h8 v15 l-5,5 v50 h-30 v-50 l-5,-5 v-15 z" />
<rect x="20" y="29" width="5" height="10" fill="black"/>
</symbol>
<symbol id="mauer">
<desc>Definition fuer eine Mauer gefuellt mit Muster "Mauerwerk"</desc>
<rect id="mauer" x="0" y="0" width="160" height="90" />
</symbol>
<symbol id="tor">
<desc>Definition für das Tor</desc>
<path d="M0,50 a25,25 0 0,1 50,0 v40 h-50 z"/>
</symbol>
<symbol id="banner">
<desc>Definition für das Banner</desc>
<path d="M10,7 v28"/>
<path d="M10,10 c20,-5 -5,5 15,5 c15,0 -5,5 15,5 h5 l-3,3 l3,3 c10,3 -15,-5 -20,-5 c5,-3
-10,0 -15,-5 z"/>
</symbol>
</defs>
<g>
<rect width="160" height="200" fill="url(#hintergrund)"/>
<use href="#turm" x="10" y="56" />
<use href="#banner" x="55" y="6" fill="#c32e04"/>
<use href="#turm" x="110" y="56" />
<use href="#banner" x="5" y="22" fill="#3983ab"/>
<use href="#turm" x="60" y="40" />
<use href="#banner" x="105" y="22" fill="purple"/>
<use href="#mauer" x="0" y="110" />
<use href="#tor" x="55" y="120" fill="url(#holz)"/>
<circle r="15" fill="url(#gelb)"/>
</g>
Die Burg besteht aus nur wenigen symbol-Elementen wie z. B. der Turm oder das Banner, die dann mit use mehrfach aufgerufen werden.
Das Mauerwerk und die Holz-Optik des Tores wurde mit Hilfe eines pattern-Musters gestaltet, das dann im fill-Attribut aufgerufen wird.
Der Himmel und die Sonne wurden mit Farbverläufen gefüllt.
Tangram
<svg>
<defs>
<title>Vorlagen für Tangram-Figuren</title>
<symbol id="dreieck_gr">
<desc>das große Dreieck</desc>
<path d="m0,0 132,132 132-132z"/>
</symbol>
<symbol id="dreieck_m">
<desc>das mittlere Dreieck</desc>
<path d="m0,0 h132 v132 z"/>
</symbol>
<symbol id="dreieck_kl">
<desc>das kleine Dreieck</desc>
<path d="m0,66 l66,66v-132z"/>
</symbol>
<symbol id="quad">
<desc>das quadratische Rechteck</desc>
<path d="m0,0 v94 h94 v-94z"/>
</symbol>
<symbol id="para">
<desc>das Paralellogramm</desc>
<path d="m66,0 l-67,67 v132 l67-67 z"/>
</symbol>
<symbol id="viereck">
<text x="10" y="280">Quadrat</text>
<use href="#dreieck_gr" class="eins"/>
<use href="#dreieck_gr" class="zwei" transform="scale(-1 1) rotate(90)"/>
<use href="#dreieck_m" class="drei" transform="translate(264,132) rotate(90)"/>
<use href="#dreieck_kl" class="vier" x="132" y="66"/>
<use href="#quad" class="sechs" transform="translate(132,132) rotate(45)"/>
<use href="#para" class="sieben" x="198" y="0"/>
<use href="#dreieck_kl" class="fuenf" transform="translate(132,197) rotate(90)"/>
</symbol>
<symbol id="schiff">
<text x="40" y="250">Schiff</text>
<use href="#dreieck_gr" class="eins" transform="translate(140,0) rotate(90)"/>
<use href="#dreieck_gr" class="zwei" transform="translate(290,0) rotate(90)"/>
<use href="#dreieck_m" class="drei" transform="translate(400,80) scale(-1 1) rotate(45)"/>
<use href="#dreieck_kl" class="vier" transform="translate(281,218) rotate(45)"/>
<use href="#para" class="sieben" transform="translate(374,218) rotate(45) "/>
<use href="#dreieck_kl" class="fuenf" transform="translate(187,311) rotate(135)"/>
<use href="#quad" class="sechs" transform="translate(140,264)"/>
</symbol>
</defs>
<use href="#viereck" x="10" y="10"/>
<use href="#schiff" x="400" y="10"/>
</svg>
Tangram ist ein chinesisches Legespiel, in dem ein Quadrat in 7 geometrische Formen zerschnitten wird, die dann beliebig zu neuen Bildern gedreht, gespiegelt und verschoben werden.
Da es beim Rotieren, Spiegeln und Verschieben zu Rundungsfehlern kommen kann, wurde den Figuren ein 1 Einheiten breiter weißer Rand gegeben, der das Gesamtbild besonders im ersten Beispiel gleichmäßiger aussehen lässt.
Weblinks
- W3C: Use-Element
- W3C: Symbol-Element
- Sara Soueidan: Structuring, Grouping, and Referencing in SVG
- Sara Soueidan: Styling SVG <use> Content with CSS (codrops, 16.7.2015)
- CSS-Tricks.com: SVG `use` with External Source
- CSS-Tricks.com: Use and Reuse Everything in SVG… Even Animations! Sehr cooles Beispiel, das use-Elemente mit CSS-Variablen stylt und einen Würfel aus Einzelteilen zusamenstellt, der bei :hover auseinanderfliegt.
SVG-use, Shadow DOM und custom properties
- css-tricks: Encapsulating Style and Structure with Shadow DOM von Caleb Williams, Mar 22, 2019
- css-tricks: Custom Properties and Shadow DOM Video von Chris Coyier, 29.07.2020