Kürzlich musste ich eine Webseite erstellen, die eine Reihe von SVG-Diagrammen für ein Analyse-Dashboard anzeigte. Ich verwendete eine Reihe von <rect>-, <line>- und <text>-Elementen in jedem Diagramm, um bestimmte Metriken zu visualisieren.
Das funktioniert und wird problemlos dargestellt, führt aber zu einem aufgeblähten DOM-Baum, bei dem jede Form als separate Knoten dargestellt wird. Die gleichzeitige Anzeige aller 50 Graphen auf einer Webseite führt zu insgesamt 5.951 DOM-Elementen, was viel zu viel ist.

Dies ist aus mehreren Gründen nicht optimal
- Ein großes DOM erhöht den Speicherverbrauch, längere Stilberechnungen und kostspielige Layout-Reflows.
- Es vergrößert die Dateigröße auf der Client-Seite.
- Lighthouse bestraft die Performance- und SEO-Scores.
- Die Wartbarkeit ist ein Albtraum – selbst wenn wir ein Templating-System verwenden –, da immer noch viel Müll und Wiederholungen vorhanden sind.
- Es ist nicht skalierbar. Das Hinzufügen weiterer Graphen verschärft diese Probleme nur.
Wenn wir uns die Graphen genauer ansehen, sehen wir viele wiederholte Elemente.

Hier ist ein Dummy-Markup, das den verwendeten Graphen ähnelt
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="500"
height="200"
viewBox="0 0 500 200"
>
<!--
📊 Render our graph bars as boxes to visualise our data.
This part is different for each graph, since each of them displays different sets of data.
-->
<g class="graph-data">
<rect x="10" y="20" width="10" height="80" fill="#e74c3c" />
<rect x="30" y="20" width="10" height="30" fill="#16a085" />
<rect x="50" y="20" width="10" height="44" fill="#16a085" />
<rect x="70" y="20" width="10" height="110" fill="#e74c3c" />
<!-- Render the rest of the graph boxes ... -->
</g>
<!--
Render our graph footer lines and labels.
-->
<g class="graph-footer">
<!-- Left side labels -->
<text x="10" y="40" fill="white">400k</text>
<text x="10" y="60" fill="white">300k</text>
<text x="10" y="80" fill="white">200k</text>
<!-- Footer labels -->
<text x="10" y="190" fill="white">01</text>
<text x="30" y="190" fill="white">11</text>
<text x="50" y="190" fill="white">21</text>
<!-- Footer lines -->
<line x1="2" y1="195" x2="2" y2="200" stroke="white" strokeWidth="1" />
<line x1="4" y1="195" x2="2" y2="200" stroke="white" strokeWidth="1" />
<line x1="6" y1="195" x2="2" y2="200" stroke="white" strokeWidth="1" />
<line x1="8" y1="195" x2="2" y2="200" stroke="white" strokeWidth="1" />
<!-- Rest of the footer lines... -->
</g>
</svg>
Und hier ist eine Live-Demo. Während die Seite einwandfrei gerendert wird, wird das Markup des Graphen-Footers ständig neu deklariert und alle DOM-Knoten werden dupliziert.
Die Lösung? Das SVG-Element.
Glücklicherweise verfügt SVG über ein <use>-Tag, mit dem wir etwas wie unseren Graphen-Footer nur einmal deklarieren und dann von überall auf der Seite einfach darauf verweisen können, um es beliebig oft darzustellen. Von MDN
Das
<use>-Element nimmt Knoten aus dem SVG-Dokument und dupliziert sie an anderer Stelle. Die Auswirkung ist dieselbe, als ob die Knoten tief in ein nicht zugängliches DOM geklont und dann dort eingefügt würden, wo sich dasuse-Element befindet.
Das ist genau das, was wir wollen! In gewisser Weise ist <use> wie eine modulare Komponente, die es uns ermöglicht, Instanzen desselben Elements überall einzufügen, wo wir möchten. Anstatt jedoch Props und Ähnliches zum Befüllen des Inhalts zu verwenden, verweisen wir darauf, welcher Teil der SVG-Datei wir anzeigen möchten. Für diejenigen unter Ihnen, die mit Grafikprogrammier-APIs wie WebGL vertraut sind, wäre eine gute Analogie Geometry Instancing. Wir deklarieren das zu zeichnende Element einmal und können es dann als Referenz wiederverwenden, während wir die Position, Skalierung, Rotation und Farben jeder Instanz ändern können.
Anstatt die Fußzeilen und Beschriftungen unseres Graphen für jede Graphinstanz einzeln zu zeichnen und sie dann immer wieder mit neuem Markup neu zu deklarieren, können wir den Graphen einmal in einer separaten SVG rendern und einfach darauf verweisen, wenn er benötigt wird. Das <use>-Tag ermöglicht es uns, Elemente von anderen Inline-SVG-Elementen problemlos zu referenzieren.
Lassen Sie uns es in Gebrauch nehmen
Wir werden die SVG-Gruppe für den Graphen-Footer – <g class="graph-footer"> – in ein separates <svg>-Element auf der Seite verschieben. Es wird auf dem Frontend nicht sichtbar sein. Stattdessen wird dieses <svg> mit display: none versteckt und enthält nur eine Reihe von <defs>.
Und was genau ist das <defs>-Element? MDN rettet uns wieder einmal.
Das
<defs>-Element wird verwendet, um Grafikobjekte zu speichern, die später verwendet werden sollen. Objekte, die innerhalb eines<defs>-Elements erstellt werden, werden nicht direkt gerendert. Um sie anzuzeigen, müssen Sie sie referenzieren (z. B. mit einem<use>-Element).
Bewaffnet mit diesen Informationen, hier ist der aktualisierte SVG-Code. Wir werden ihn ganz oben auf der Seite platzieren. Wenn Sie Templates verwenden, würde dies in einer Art globalem Template, wie einem Header, landen, damit es überall enthalten ist.
<!--
⚠️ Notice how we visually hide the SVG containing the reference graphic with display: none;
This is to prevent it from occupying empty space on our page. The graphic will work just fine and we will be able to reference it from elsewhere on our page
-->
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="500"
height="200"
viewBox="0 0 500 200"
style="display: none;"
>
<!--
By wrapping our reference graphic in a <defs> tag we will make sure it does not get rendered here, only when it's referenced
-->
<defs>
<g id="graph-footer">
<!-- Left side labels -->
<text x="10" y="40" fill="white">400k</text>
<text x="10" y="60" fill="white">300k</text>
<text x="10" y="80" fill="white">200k</text>
<!-- Footer labels -->
<text x="10" y="190" fill="white">01</text>
<text x="30" y="190" fill="white">11</text>
<text x="50" y="190" fill="white">21</text>
<!-- Footer lines -->
<line x1="2" y1="195" x2="2" y2="200" stroke="white" strokeWidth="1" />
<line x1="4" y1="195" x2="2" y2="200" stroke="white" strokeWidth="1" />
<line x1="6" y1="195" x2="2" y2="200" stroke="white" strokeWidth="1" />
<line x1="8" y1="195" x2="2" y2="200" stroke="white" strokeWidth="1" />
<!-- Rest of the footer lines... -->
</g>
</defs>
</svg>
Beachten Sie, dass wir unserer Gruppe die ID graph-footer gegeben haben. Das ist wichtig, da es der Haken ist, wenn wir nach <use> greifen.
Wir fügen also ein weiteres <svg> auf der Seite ein, das die benötigten Graph-Daten enthält, und referenzieren dann #graph-footer in <use>, um den Footer des Graphen zu rendern. Auf diese Weise müssen wir den Code für den Footer nicht für jeden einzelnen Graphen neu deklarieren.
Sehen Sie, wie viel sauberer der Code für eine Graphinstanz ist, wenn <use> in ... äh, Gebrauch ist.
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="500"
height="200"
viewBox="0 0 500 200"
>
<!--
📊 Render our graph bars as boxes to visualise our data.
This part is different for each graph, since each of them displays different sets of data.
-->
<g class="graph-data">
<rect x="10" y="20" width="10" height="80" fill="#e74c3c" />
<rect x="30" y="20" width="10" height="30" fill="#16a085" />
<rect x="50" y="20" width="10" height="44" fill="#16a085" />
<rect x="70" y="20" width="10" height="110" fill="#e74c3c" />
<!-- Render the rest of the graph boxes ... -->
</g>
<!--
Render our graph footer lines and labels.
-->
<use xlink:href="graph-footer" x="0" y="0" />
</svg>
Und hier ist ein aktualisiertes <use>-Beispiel ohne visuelle Änderung
Problem gelöst.
Was, Sie wollen Beweise? Vergleichen wir die Demo mit der <use>-Version mit der Originalversion.
| DOM-Knoten | Dateigröße | Dateigröße (GZIP-Kompression) | Speichernutzung | |
|---|---|---|---|---|
Kein <use> | 5,952 | 664 KB | 40,8 KB | 20 MB |
Mit <use> | 2,572 | 294 KB | 40,4 KB | 18 MB |
| Einsparungen | 56 % weniger Knoten | 42 % kleiner | 0,98 % kleiner | 10 % weniger |
Wie Sie sehen, ist das <use>-Element sehr nützlich. Und obwohl die Leistungsverbesserungen hier im Vordergrund standen, macht allein die Tatsache, dass es riesige Codeblöcke aus dem Markup entfernt, die Wartung des Ganzen zu einer wesentlich besseren Entwicklererfahrung. Doppelt gewonnen!
Ich muss mich noch in die Welt von SVG vertiefen, aber das sieht fantastisch aus, tolle Artikel!
Danke für diesen großartigen Input! Ich habe das Gefühl, dass wir viel mehr mit diesen SVGs machen könnten, als wir es derzeit tun. (Ich weiß, ich tue es)
Merci!
Sie könnten auch in Erwägung ziehen,
path-Elemente zu verwenden und alle gleichfarbigen Balken als einen einzigen Pfad zu rendern. Ist möglicherweise keine Option, wenn Sie möchten, dass die Balken einzeln interaktiv sind, z. B. mit einem Tooltip.Dies ist eine der besten SVG-Funktionen und die Grundlage für SVG-Spritemaps. Ein Vorbehalt jedoch: Wenn Sie Ihre SVGs auf einer anderen Domäne als der, auf der sie serviert werden, hosten, ist dies noch nicht vollständig CORS-konform, sodass nichts gerendert wird. Schade.
Ich habe bei MDN bemerkt, dass xlink:href in svg2 als veraltet gilt, sollten wir also einfach href verwenden? Funktioniert es auf die gleiche Weise?
Das stimmt, es ist veraltet und Sie sollten stattdessen nur href anstelle von xlink:href verwenden.
Sie könnten auch die Lesbarkeit und Wartbarkeit durch die Verwendung von Web Components verbessern. Sie könnten ein benutzerdefiniertes Element wie
<metric-graph points="...">erstellen und das SVG dynamisch generieren (natürlich mit<use>!). So könnte eine Reihe von Graphen wie folgt aussehen:<article class="graph-grid"><metric-graph points="-0.5 7.2 3.1"/><metric-graph points="1.2 -2.4 3.8"/></article>+1 für die Umsetzung mit Web Components
Die Idee sieht ziemlich interessant aus, und vielleicht könnten Sie sie weiter ausbauen
Wenn Sie serverseitig eine Sprache für den Seitenaufbau haben (PHP, Python, was auch immer), erhalten Sie wahrscheinlich Ihre Rohdaten als Array oder Liste.
Sie könnten Folgendes testen
1) Für jedes zu zeichnende Rechteck berechnen Sie eine ID, die Farbe und Höhe des Rechtecks angibt.
2) Wenn diese ID bereits zuvor berechnet wurde, verwenden Sie sie. Wenn nicht, zeichnen Sie sie und weisen Sie ihr eine ID zu.
Sie können es sogar im "display none svg" speichern, nur wenn es mindestens zweimal verwendet wird, und nicht, wenn es einzigartig ist, um die Einsparungen bei Knoten und Code zu maximieren.
Ja, das "use"-Element von SVG ist sehr nützlich! Zum Beispiel in dieser Demo ist jeder Punkt ein use-Element
https://tobireif.com/demos/snake_pattern/
Hallo, sollte Ihr
usehref-Attribut nicht mit dem Pfundzeichen beginnen, wie<use xlink:href="#graph-footer" x="0" y="0" />? Ich glaube, so habe ich es in anderen Tutorials gesehen.Toller Artikel! Danke, Georgi!
Hallo,
jemand hat ein npm-Paket für SVG-Icons erstellt, das von diesem Artikel inspiriert ist
https://www.npmjs.com/package/@novyk/ikong
Das ist ja cool!! Danke, dass Sie es mich wissen lassen, ich hätte es sonst zu 100 % verpasst