Skip to main content

Which SVG technique performs best for way too many icons?

By Tyler Sticka

Published on October 28th, 2021

Topics

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.

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.

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.

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.

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>Code language: Handlebars (handlebars)

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

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>Code language: Handlebars (handlebars)

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).

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>Code language: Handlebars (handlebars)

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).

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

<img src="path/to/icon/color.svg" alt="">Code language: Handlebars (handlebars)

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.

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="">Code language: Handlebars (handlebars)

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

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="">Code language: Handlebars (handlebars)

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

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>Code language: Handlebars (handlebars)

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>Code language: Handlebars (handlebars)

And with filters, which performed significantly worse:

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

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>Code language: Handlebars (handlebars)

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

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.

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 Scour or 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).

Comments

Lev Solntsev said:

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).

Aaron Davidson said:

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 to Aaron Davidson

Tyler Sticka (Article Author ) replied:

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!

Johan said:

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.