Which SVG technique performs best for way too many icons?

Written by Tyler Sticka on

When I started giving talks about SVG back in 2016, I’d occasionally hear a question I never had a great answer for: What if you have a lot of icons on a page?

You see, many attendees were interested in transitioning away from icon fonts. But while SVG offers a more accessible, reliable and powerful alternative, there’s no denying that browsers have an easier time displaying thousands of text characters than they do thousands of images (SVG or otherwise).

Still, it was an easy question to dodge. A typical inline SVG icon takes a fraction of a millisecond to render: When I’d ask the attendee what sort of interface required enough icons for that to add up, they’d admit that the question was hypothetical. I’d say something about the variety of SVG inclusion methods and the importance of performance testing, attendees would nod in a satisfied way and we’d all move on.

But then a few weeks ago, a client asked us to improve performance of a component that could include up to 200 rows of content, each row containing a handful of inline SVG icons. The improvements my talented colleagues eventually landed on would have more to do with DOM manipulation strategies than SVG, but along the way I was asked for advice: What’s the most performant method for including this many SVG icons at once?

It was time to stop dodging the question, roll up my sleeves and figure this out.

Considerations and caveats

This article focuses specifically on performance of large amounts of SVG icons.

If you have fewer than a hundred icons visible at a time, you probably don’t need to sweat your technique too much: The differences between one technique and another are a fraction of a millisecond per icon.

Icons are just one of SVG’s many use cases, some demanding greater complexity and their own performance considerations. For a great example of that, see Georgi Nikoloff’s article about SVG charts.

I won’t be comparing performance of SVG to other image or bundles formats. If you aren’t familiar with the pros and cons of SVG images, you can check out my talk and my team’s articles, read Chris Coyier’s Practical SVG and follow everything Sara Soueidan writes on the subject.

This many icons might hint at deeper design problems. You may be relying too much on iconography to communicate key concepts, or composing too many elements in a long scroll where pagination might be preferred.

Methodology

Screenshot of the test page in action

My test page uses vanilla HTML, CSS and JavaScript (to avoid any framework-related bias or side effects). It generates X number of HTML strings, shuffles the resultant array, appends the whole chunk of HTML, and reports the time taken to render.

The rendering time is determined by calling performance.now() on the animation frame before the HTML is appended, and again on the animation frame after. (The timestamp received by requestAnimationFrame isn’t used because it may be influenced by the display’s refresh rate, and even 16 milliseconds is enough to throw off results.)

For the results discussed here, two sets of icons were used. The first includes five optimized SVGs from our upcoming pattern redesign. The second set uses unoptimized versions of the same five: These were created by copying the contents of the optimized versions in Illustrator, pasting them into Sketch, and exporting without cleanup. These unoptimized versions have a bit of extra cruft (unnecessary group elements, masks and attributes) and a larger file size.

Icon Optimized size Unoptimized size
arrow-down-right 296 Bytes 684 Bytes
bell 488 Bytes 1.08 KB
envelope 245 Bytes 1.21 KB
heart 265 Bytes 946 Bytes
magnifying-glass 254 Bytes 759 Bytes

You can try the test page yourself (unoptimized icon version here), and the source is available on GitHub.

For each of the techniques I’ll discuss in this article, I tested 1,000 icons ten times per set in Chrome (Android, Mac), Edge (Mac), Firefox (Mac), Safari (iOS, Mac) and Samsung Internet (Android). I then compared the average and median results per technique, as well as the standard deviation to get a sense of consistency. You can view the full data set if spreadsheets are your jam.

Breakdown by technique

Icon set Inline SVG Symbol Sprite External Sprite Image Image +Data URI Image +Filter Background Background +Data URI Background +Filter Mask Mask +Data URI Optimized 74.91 ms 98.61 ms 126.04 ms 76.35 ms 67.24 ms 112.35 ms 110.16 ms 109.14 ms 147.87 ms 148.87 ms 144.94 ms Unoptimized 191.89 ms 164.85 ms 180.51 ms 81.87 ms 77.76 ms 113.56 ms 112.8 ms 120.55 ms 149.22 ms 153.25 ms 149.65 ms

Chart: Average time in milliseconds to render 1,000 icons.

Inline SVG

An inline SVG is just an HTML svg element with its contents (paths, etc.) included directly within. There’s no fancy bundling or sprite strategy happening here: If the icon occurs many times on the same page, its contents are repeated each time.

<svg viewBox="0 0 24 24" width="24" height="24">
  <!-- paths, shapes, etc. -->
</svg>

For the optimized icons, inline SVG was consistently one of the most performant techniques. But for the unoptimized icons, it was often the slowest.

Symbol Sprite

In this technique, the actual visual data of each icon is represented as a symbol element within a single SVG. Individual icons are displayed via a use element referencing the desired symbol.

<svg style="display:none">
  <symbol id="example" viewBox="0 0 24 24" width="24" height="24">
    <!-- paths, shapes, etc. -->
  </symbol>
</svg>

<svg><use href="#example"/></svg>

The performance of this technique relative to Inline SVG seems to be dependent on the total number of elements reduced. For the optimized icons, this technique was always slower. For the unoptimized icons, it was often faster (though not consistently).

External symbol sprite

I also tested a variation of the previous technique where the symbol elements are stored in an external SVG file:

<svg><use href="path/to/sprite.svg#example"/></svg>

The results varied wildly between browsers. In Safari, external symbol sprites outperformed every other technique regardless of optimization. In Firefox and Samsung Internet Browser, external sprites modestly outperformed in-page sprites. But in Chrome and Edge, external symbol sprites were by far the slowest technique (and often the most inconsistent).

Image element

Like all image formats, SVG files may be displayed using img elements.

<img src="path/to/icon/color.svg" alt="">

Because the SVG is external, its contents can’t inherit in-page colors the same way that previous techniques can. For my tests, I generated static color variations of all icons, but this could also be accomplished using a microservice (like this one).

There are also slight differences in how image elements render compared to inline SVG: Negative space appears slightly diminished. This will be true for all remaining techniques in this article.

Enlarged comparison of icons rendered in Chrome. The top row are rendered as inline SVG, the bottom as external img elements.

This was one of the most performant techniques regardless of browser or optimization. That said, inline SVG has a slight edge when icons were optimized, and both techniques were consistently outperformed by our next option.

Image element with data URI

Because SVGs are composed of markup rather than bitmap data, they can be efficiently converted to a data URI string without any base64 encoding. This may be used to dynamically generate and/or colorize an SVG element without requiring an external service (or loading a separate file).

<img src="data:image/svg+xml,..." alt="">

Across all browsers and regardless of optimization, this technique rendered the fastest and with the least deviation.

Image element with CSS filter

Monochromatic img elements may be colorized via a series of CSS filters, as demonstrated by Barrett Sonntag. This requires fewer unique images or data URI strings to begin with.

<img src="path/to/icon.svg" style="filter: ..." alt="">

This technique showed admirable performance in Chrome and Edge, middling performance in Firefox and Samsung Internet, and disappointing performance in Safari.

Background image

Like other image formats, SVG elements may be displayed as an HTML element’s background-image:

<div style="background-image: url(path/to/icon/color.svg);">...</div>

Background images are subject to the same limitations as img, with comparable rendering differences. Unfortunately, they do not yield comparable performance benefits, consistently under-performing compared to the image element and image element with data URI techniques.

I also tested this with a data URI, which performed slightly worse:

<div style="background-image: url(data:image/svg+xml;charset=UTF-8,...);">...</div>

And with filters, which performed significantly worse:

<div style="
  background-image: url(path/to/icon.svg); 
  filter: ...;">...</div>

Mask image

Monochromatic images may be used as the value of a mask-image, reducing the visible area of the styled element to that of the icon. This preserves the ability to inherit the CSS colors while keeping the icon in a separate file.

<div style="
  -webkit-mask-image: url(path/to/icon.svg);
  mask-image: url(path/to/icon.svg);">
  ...
</div>

Whether using a static image or a data URI, this was generally the slowest technique.

And the rest…

SVG elements may also be displayed via object and iframe elements. I started adding tests for these, but the performance was so atrocious that I abandoned them quickly to focus on more viable options.

Other methods of bundling SVG sprites using a traditional background-image, fragment identifiers, SVG stacks or defs instead of symbol were omitted for being less common in 2021, more complicated to bundle and too similar to other techniques.

Takeaways

So which technique performs best for way too many icons? It depends!

If you want all the features of SVG and your icons are well optimized, inline SVG is your best bet. Simple and performant.

If your icons are complex or poorly optimized, or if you don’t need all the bells and whistles of SVG, image elements are the most performant option, especially using data URIs (encoded as escaped XML, not Base64).

No matter what technique you use, optimizing your SVGs will improve performance. Automated tools like svgo can help, but designers can make an even bigger difference by reducing elements and simplifying paths before export (see Sara Soueidan’s Tips for Creating and Exporting Better SVGs for the Web).

Acknowledgments

Tyler Sticka

Tyler Sticka is Cloud Four’s VP of Design, allowing him to think about design systems every day. When he isn’t directing his team, sketching on sticky notes or nitpicking CSS, he enjoys reading comics, making video games and listening to weird music. He tweets as @tylersticka.

Never miss an article!

Get Weekly Digests


Comments

Add a comment

Hey Tyler
Don’t all SVG elements in an HTML document need their xmlns attribute declared?

Nice write-up!

Inline SVGs still have one notable caveat: they have subpixel rendering. So if you center an area where icon is placed (like in popup), your precisely pixel-aligned icons may appear blurry due to not being aligned by pixel edges. Just because something like 25% of 71 pixel is not an integer. Images or background-images are rendered more pleasable, just like raster images.

Also, as ex-SVGO maintainer I must note, that “;charset=UTF-8” in data-URI is totally useless, just a waste of bytes. I haven’t found a way to change charset that way or any impact of it, most likely you will not have non-ascii characters in SVGO, and lastly, generally it’s a bad idea not to use UTF-8 in 2021.

There is a great online encoder for data-uri SVGs: https://yoksel.github.io/url-encoder/ Modern browsers (i.e. not IE) also don’t require to encode symbols like “<” and “>”, mostly one need to encode only “#” and “?”—as they have special meaning in urls—and perhaps quotes (can be avoided in certain conditions).

Replies

Thanks, Lev!

The subpixel rendering comment is a good thing to keep in mind. When it comes to which rendering technique is preferable, I think that may be subjective. I tend to find img and background-image a bit “fuzzy” to my eye. I wish shape-rendering gave us more options for this kind of thing.

I’ve removed ;charset=UTF-8 from the data URI example, thanks for the info!

Thanks for this, lots of valuable info in the article, links and comments.
In case some think this is what they should be doing, it should be pointed out that your results only apply to content generated client side.
You are measuring the rendering speed of inserting preloaded data into the DOM and not the overall speed of downloading that data and THEN appending it.
Repetition of non-cacheable data URIs would be overall far slower in server-side pre-rendered HTML and perceived performance would be far worse.

Replies

That’s a great point, Aaron: This article focuses on client-side rendering speed specifically. I considered including more info on server-side performance early on, but my earliest findings seemed to be less about SVG icons and more about best practices for image loading and server configuration.

That said, I’d be a bit surprised to hear that non-base64 SVG data URIs are universally far slower for pre-rendered HTML, at least compared to inline SVG (which is also repetitive and non-cacheable). But you may have a point when it comes to pre-rendered img elements!

I appreciate all the hard work here but you are comparing all SVG techniques against each other, whereas the posts starts with the point that icons fonts are believed to be faster, so why not do that? Or would that be comparing apples and oranges?

I still believe that that is the fastest technique to display thousands of icons, by far. You don’t have to pull all this (mostly) nonsensical markup over the wire too.

Leave a Comment

Please be kind, courteous and constructive. You may use simple HTML or Markdown in your comments. All fields are required.


Let’s discuss your project! Email Us