SVG Sprites Uncovered: A Deep Dive into Existing Solutions and Innovations
Intro 🎬
In this article, we’ll uncover a powerful technique for image optimization—SVG sprites. Not only will we explore the leading solutions available today, but we’ll also dive into how you can craft your own custom solution to tackle specific challenges.
Problem 🚩
Imagine you are asked to design a dashboard like instabug
As demonstrated in the previous image, the home page contains over 40 icons. If each icon were treated as a separate image, this would result in over 40 individual network requests just for the icons, in addition to requests for bundles and backend resources.
This can easily exceed network limitations, leading to congestion and significantly degrading overall performance by increasing latency and consuming available bandwidth.
Even after loading these icons, we may encounter a scenario where additional icons are needed for another page. This would further impact performance during route changes, resulting in a poor user experience.
Solution 💡
By combining all SVGs into a single file and referencing individual icons as needed, we can limit the network requests to just one, but how 🤔?
by understanding the nature of SVG we will know how to do that.
SVG (Scalable Vector Graphics) are vector-based and use mathematical equations to define shapes and colors.
SVG's mathematical nature enables the sprite feature, which wraps each graphic in a <symbol>
element with a unique ID and combines them into a single SVG file.
We have two technique for creating SVG spritemap
:internal reference
and external reference
Internal reference:
in internal reference
, we create spritemap
and inject it to the DOM
Example:
In this example, we place the content of the attachment
and bell
icons between <symbol>
tags, assigning each symbol a unique ID that can be referenced later using xlink:href
.
External reference
In external referencing
, the spritemap
is stored in an external file, and its name is referenced in the DOM.
Example:
- create
spritemap.svg
file
<svg xmlns="http://www.w3.org/2000/svg" width="0" height="0">
<symbol id="attachment" viewBox="0 0 395.449 395.449">
<path d="M357.744,60.411c-0.01-0.01-0.021-0.021-0.033-0.032c-24.096-24.096-59.338-39.665-89.789-39.666
c-32.213,0-62.475,12.52-85.213,35.255L28.16,210.517C10.004,228.673,0.004,252.832,0,278.551
c0.002,25.71,9.996,49.863,28.146,68.015c0.004,0.005,0.01,0.01,0.014,0.015c18.154,18.149,42.313,28.149,68.029,28.156
c0.018,0,0.037-0.002,0.055,0c25.701-0.012,49.846-10.012,67.988-28.158l137.25-137.243c4.803-4.756,20.479-22.251,20.545-47.657
c0.03-11.833-3.388-29.394-19.849-45.855c-18.477-18.477-37.973-21.26-51.075-20.341c-15.914,1.116-31.23,8.282-43.128,20.18
L96.251,227.384c-7.811,7.812-7.811,20.474,0,28.284c7.811,7.81,20.473,7.811,28.285,0l111.726-111.727
c5.006-5.005,11.437-8.125,17.642-8.562c6.987-0.49,13.715,2.445,19.991,8.724c5.412,5.412,8.15,11.288,8.134,17.467
c-0.025,10.242-7.244,17.907-8.759,19.408L135.945,318.295c-10.588,10.591-24.682,16.429-39.695,16.439
c-0.035-0.002-0.07,0-0.105-0.002c-15.014-0.016-29.111-5.854-39.705-16.443c-0.004-0.004-0.004-0.004-0.008-0.008
C45.836,307.686,40,293.574,40,278.553c0.002-15.031,5.842-29.147,16.445-39.752l154.549-154.55
c15.18-15.179,35.397-23.539,56.928-23.539c19.76,0,45.047,11.493,61.512,27.956c0.007,0.007,0.015,0.015,0.021,0.021
c16.033,16.035,25.994,38.852,25.994,59.549c0,21.53-8.357,41.747-23.539,56.926l-53.988,53.979
c-7.812,7.812-7.813,20.474-0.002,28.284c0,0.001,0,0.001,0.002,0.002c7.81,7.811,20.471,7.812,28.279,0.001l53.992-53.981
c22.735-22.734,35.256-52.997,35.256-85.211C395.449,116.85,381.354,84.02,357.744,60.411z"></path>
</symbol>
<symbol id="bell" fill="#000000" viewBox="0 0 500 500">
<path d="M 366.25,212.825c-57.00-122.675-86.625-169.275-179.775-167.325c-33.175,0.675-25.20-24.025-50.50-14.65 C 110.70,40.20, 132.40,53.925, 106.525,75.15c-72.55,59.55-65.875,114.65-32.225,246.00c 14.20,55.30-34.175,58.025-15.05,111.65 c 13.975,39.075, 116.975,55.475, 225.65,15.125c 108.675-40.30, 177.25-120.325, 163.275-159.45C 429.05,234.875, 390.275,264.475, 366.25,212.825z M 273.10,414.90 c-97.025,36.00-176.80,14.825-180.175,5.425c-5.80-16.225, 31.325-70.40, 142.275-111.575c 110.95-41.175, 172.875-25.90, 179.35-7.775 C 418.375,311.675, 370.175,378.875, 273.10,414.90z M 241.925,327.55c-50.75,18.825-86.00,40.35-108.825,59.75 c 16.075,14.60, 46.15,18.15, 76.15,7.025c 38.175-14.175, 61.625-46.70, 52.35-72.625c-0.10-0.325-0.275-0.60-0.40-0.90 C 254.95,322.825, 248.525,325.075, 241.925,327.55z"></path>
</symbol>
</svg>
- utilize external
spritemap
in your document
<!DOCTYPE html>
<html lang="en">
<head>
<title>spritemap external</title>
</head>
<body>
<svg>
<use xlink:href="spritemap.svg#attachment"></use>
</svg>
<svg>
<use xlink:href="spritemap.svg#bell"></use>
</svg>
</body>
</html>
There are many online tools available that can automatically generate SVG sprite maps. One such tool is SVGSprite
Limitation:
If your SVG includes a <LinearGradient>
element, the linear gradient color may not be rendered correctly. (browser support issue)
Thought 💭
I can read questions running on your mind now
“How can I use this approach in production if I have a thousand icons?”. “Should I upload them to one of these tools and then append the output to my root file?“ ☹️
Definitely short answer is NO 🙅
Uploading images is just one part of the challenge. Another issue is managing updates: if you add a new icon to your folder, you need to regenerate the spritemap
or manually create a new <symbol>
and include the icon's content within it.
We need to set up a task runner that executes each time we build our project to handle generating spritemap
image automatically. Here, Webpack
's role truly shines.
There are several plugins available that serve this purpose. Below, we will explore how each plugin functions, their respective drawbacks, and the reasons behind our decision to develop a new solution
webpack-svgstore-plugin 🔗
Initially, we used a plugin based on the internal referencing
technique. You can refer to their doc for setup and usage instructions.
BUT
You need to take the following factors into consideration:
Project scale: if your SVGs folder contains a large number of icons, all of them will be injected into the DOM, leading to DOM bloating and potential performance issues.
Webpack version: if you're using
Webpack5
or planning to migrate to it, you'll need to find an alternative solution. This plugin doesn't supportWebpack5
and the maintainer has no plans to support it, as the plugin's repository was archived on March 18, 2023.
svg-spritemap-webpack-plugin 🔗
Both previous issues are unacceptable, especially for large web applications where performance is a necessity, not a luxury.
Therefore, we searched for a plugin that utilizes an external reference technique.
@ Instabug, we explored adopting svg-spritemap-webpack-plugin
in our codebase, but we encountered an issue with ID collisions.
Let’s walk through a simple example to illustrate the issue, Consider the following two SVG icons:
Block icon
Info icon
Generate
spritmap
for both SVGs using SVGSprite
“What happened ?!” , “why info icon disappeared ?!!” 😲
The issue arises because both <path>
tags inside the <symbol>
elements have the same id
("path-1"), causing conflicts when referenced. The browser displays the same icon twice since the <use>
element refers to the first matching id
.
Solution:
We need to append the SVG file name to the ID, as the file name is unique for each SVG
SO , what are the problems of svg-spritemap-webpack-plugin ?
let’s examine this code
// code snippet from svg-spritemap-webpack-plugin
const config = generateSVGOConfig(
outputOptions.svgo,
[
{
name: 'removeTitle',
active: !spriteOptions.generate.title,
},
],
[
{
name: 'cleanupIDs',
active: false,
},
],
)
In this snippet, svg-spritemap-webpack-plugin
utilizes SVGO for optimizing SVG files, but it disables the cleanupIDs
plugin, which leads to the ID collision issue we previously discussed.
The previously mentioned issue is not the only limitation of thesvg-spritemap-webpack-plugin
. It overlooks the linearGradient
problem we discussed earlier, and continues to include SVGs containing linearGradient
elements in the spritemap
output without addressing the issue.
simple-svg-sprite 🔗
For more flexibility, we have decided to develop our own simple custom solution. Building a spritemap
algorithm is relatively straightforward and will allow us to make adjustments easily, without relying on maintainer support.
Generate spritemap algorithm
It can be employed in any project that uses Webpack
as a bundler, providing a general solution for generating spritemap
.This is not specifically customized for the instabug codebase.
Check this demo to discover more
Below is a summary comparing the three plugins:
webpack-svgstore-plugin | svg-spritemap-webpack-plugin | simple-svg-sprite | |
reference method | Internal | External | External |
Webpack support | 4 only | 4 and 5 | 4 and 5 |
ID collisions | NOT Existed | Existed | NOT Existed |
Unpacked Size | 75 kB | 79.6 kB | 7.23 kB |
Subscribe to my newsletter
Read articles from mohamed sayed directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
mohamed sayed
mohamed sayed
Software engineer at Instabug | Frontend team. Passionate about web development.