Recently, I had to make a web page displaying a bunch of SVG graphs for an analytics dashboard. I used a bunch of <rect>
, <line>
and <text>
elements on each graph to visualize certain metrics.
This works and renders just fine, but results in a bloated DOM tree, where each shape is represented as separate nodes. Displaying all 50 graphs simultaneously on a web page results in 5,951 DOM elements in total, which is far too many.
This is not optimal for several reasons:
- A large DOM increases memory usage, longer style calculations, and costly layout reflows.
- It will increases the size of the file on the client side.
- Lighthouse penalizes the performance and SEO scores.
- Maintainability is a nightmare — even if we use a templating system — because there’s still a lot of cruft and repetition.
- It doesn’t scale. Adding more graphs only exacerbates these issues.
If we take a closer look at the graphs, we can see a lot of repeated elements.
Here’s dummy markup that’s similar to the graphs we’re using:
<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>
And here is a live demo. While the page renders fine the graph’s footer markup is constantly redeclared and all of the DOM nodes are duplicated.
The solution? The SVG element.
Luckily for us, SVG has a <use>
tag that lets us declare something like our graph footer just once and then simply reference it from anywhere on the page to render it as many times as we want. From MDN:
The
<use>
element takes nodes from within the SVG document, and duplicates them somewhere else. The effect is the same as if the nodes were deeply cloned into a non-exposed DOM, then pasted where theuse
element is.
That’s exactly what we want! In a sense, <use>
is like a modular component, allowing us to drop instances of the same element anywhere we’d like. But instead of props and such to populate the content, we reference which part of the SVG file we want to display. For those of you familiar with graphics programming APIs, such as WebGL, a good analogy would be Geometry Instancing. We declare the thing we want to draw once and then can keep reusing it as a reference, while being able to change the position, scale, rotation and colors of each instance.
Instead of drawing the footer lines and labels of our graph individually for each graph instance then redeclaring it over and over with new markup, we can render the graph once in a separate SVG and simply start referencing it when needed. The <use>
tag allows us to reference elements from other inline SVG elements just fine.
Let’s put it to use
We’re going to move the SVG group for the graph footer — <g class="graph-footer">
— to a separate <svg>
element on the page. It won’t be visible on the front end. Instead, this <svg>
will be hidden with display: none
and only contain a bunch of <defs>
.
And what exactly is the <defs>
element? MDN to the rescue once again:
The
<defs>
element is used to store graphical objects that will be used at a later time. Objects created inside a<defs>
element are not rendered directly. To display them you have to reference them (with a<use>
element for example).
Armed with that information, here’s the updated SVG code. We’re going to drop it right at the top of the page. If you’re templating, this would go in some sort of global template, like a header, so it’s included everywhere.
<!--
⚠️ 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>
Notice that we gave our group an ID of graph-footer
. This is important, as it is the hook for when we reach for <use>
.
So, what we do is drop another <svg>
on the page that includes the graph data it needs, but then reference #graph-footer
in <use>
to render the footer of the graph. This way, there’s no need to redeclaring the code for the footer for every single graph.
Look how how much cleaner the code for a graph instance is when <use>
is in.. umm, use.
<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>
And here is an updated <use>
example with no visual change:
Problem solved.
What, you want proof? Let’s compare the demo with <use>
version against the original one.
DOM nodes | File size | File Size (GZIP compression) | Memory usage | |
---|---|---|---|---|
No <use> |
5,952 | 664 KB | 40.8 KB | 20 MB |
With <use> |
2,572 | 294 KB | 40.4 KB | 18 MB |
Savings | 56% fewer nodes | 42% smaller | 0.98% smaller | 10% less |
As you can see, the <use>
element comes in handy. And, even though the performance benefits were the main focus here, just the fact that it reduces huge chunks of code from the markup makes for a much better developer experience when it comes to maintaining the thing. Double win!
More information
Use and Reuse Everything in SVG… Even Animations!
SVG `use` with External Reference, Take 2
How to Make Charts with SVG
A Handmade SVG Bar Chart (featuring some SVG positioning gotchas)
13: SVG as an Icon System – The `use` Element
15: SVG Icon System – Where the defs go
The post Too Many SVGs Clogging Up Your Markup? Try `use`. appeared first on CSS-Tricks.
You can support CSS-Tricks by being an MVP Supporter.
source https://css-tricks.com/too-many-svgs-clogging-up-your-markup-try-use/
No comments:
Post a Comment