Icons — A Big Mystery for Web browsers
When we are designing User Interfaces, we certainly always use icons to make it visually appealing ,user friendly, and also save some canvas space by not showing text and replacing them with self explanatory icons.
There are multiple ways of adding Icons, each having it’s own pros and cons.
I wanted to use the approach that would, minimise my effort while coding 😝 , is best for website performance in comparison to other techniques and doesn’t degrade end user experience, for our website housing.com.
1. Use PNG/JPEG images:
We can have icons as JPEG/PNG images, and place them by using <img> tag.
Biggest pro of this method is that it is a quite simplistic approach. Whatever icons we need we will simply import their images.
But by using this method, there would be a dramatic loss in quality when same icon image is scaled at different sizes and hence would require us to have multiple images for different screen resolutions. This means extra resources to load. Additionally, if we wanna change icons on hover/click, it would require a new network call which may result in slow UI change for people with slow internet. Also, there would be a lot of network calls just after page load when we require multiple icons in our first fold.
2. Using image sprites:
An image sprite is merely a collection of separate individual images put together to form a single image. Image sprite arranges all icons in a single GIF or PNG file and is loaded as a CSS background image.
By adjusting the CSS background property, only the required icon is displayed. eg. in the below snippet, we load spriteImage.png and display the icon which was 20px in horizontal and vertical direction both.
In earlier method we required separate network calls when we require same icon at different resolutions, or changing icon on hover/click, or for completely different icons. All these network calls are eliminated by using image sprite as we can include all icons we would require in the same sprite.
But, even in this method we would require same icon at different resolutions to be included in the sprite, as only one image can’t scale up and down. Additionally, using sprite means we are downloading a large file with multiple icons which might include icons we don’t require at that particular time.
3. Using Icon Fonts:
Icon fonts are in essence simple text and are included as pseudo elements (:before or :after).
Major pros of using icon fonts is that they are able to scale up perfectly to any resolution without any degradation or loss of visual quality. We can also change size/color using CSS properties like font-size, color for the same. There are a lot of websites offering free icon font packages eg. FontAwesome, IcoMoon, FlatIcon, etc.
Many a times we may require our icons to be multi-color and icon fonts do not support multi-color icons as of now. Moreover, icon fonts suffer some browser anti-aliasing techniques when scaled.
4. Using SVG Images:
SVG stands for Scalable Vector Graphics. SVG is used to define vector-based graphics for the Web.
As the name suggests, SVG offer an unmatched ability to be scaled to any size without any shred of quality degradation and it also offers better anti-aliasing than Icon fonts. Moreover, we can edit, color, or animate each individual bit of an SVG icon, unlike icon fonts. Since SVG icons are just a block of code, their sizes are much lower to that of image based PNG JPEG icons. They also offer better accessibility, better animations than icon fonts.
We can further use SVGs in 3 different ways:
a. External SVG
This is basically storing SVG in an individual different file with the extension .svg, and including it in our project using Image or Object HTML tags.
External SVGs are cached by browser. Hence, if we require same SVG multiple times, we can use the cached one, it won’t cause network load. Also, if we need to change an SVG later then we can just change one file and it would reflect change in all places used. By using them we also don’t see multiple lines of irrelevant code in our HTML files. These all are actually cons we would face in inline SVG(as we will see next).
Again, as with simply using PNG/JPEG icons, it triggers multiple network calls for each SVG required. Moreover, SVG attributes like fill property can’t be modified when using external method. So if we wanna change color on hover/click we would have to get a separate SVG file for the same which would mean another network call.
b. Inline SVG
Inline SVG means placing SVG code block directly into our HTML/JSX code.
Inline SVG benefits us by saving the extra network call as in the case of external SVG. We can also use css ‘fill’ property and change the color.
Downside of using Inline SVG is that they clutter our HTML code and in case we have to use the same icon at multiple places (eg. fancy bullet icon in an unordered list) we will have to write the same SVG code again and again.
c. Importing SVG directly as a component (React)
If we are using create-react-app for our project, we can directly import SVG file as a component, and call that component
This basically inserts our SVG as inline. Hence it enjoys all the pros of Inline SVG, and due to the way of it’s implementation — also eliminates all the cons of Inline SVG as well.
If we are not using create-react-app, then we can also use SVGR package for the same. First install SVGR using any package installer, here we used npm.
As this is a Webpack loader. We will config the webpack.config.js
file with it.
c. SVG Sprite
This is basically including all SVGs in our project on a single go, and using one when we require. It is basically combining pros of inline and external method. An SVG sprite is also a code block. Below snippet shows a sample SVG sprite. We extract only <path> part from our original icon SVG and wrap it in <symbol> tag. This whole code is wrapped in <defs> tag which is further wrapped in <svg> tag. We can automatically create this SVG using icomoon. We can also use the sample code provided on/by https://svgsprite.com/tools/svg-sprite-generator/ or by writing a simple program which does exactly the same.
After making this sprite, we can simply include the icon we want from our HTML file. In below snippet we can change ‘id’ variable to the id of the icon we wrote in SVG sprite eg. first SVG has an id of ‘icon-buyProperties’. We can also change fill color of that by styling this SVG tag using css.
By using SVG sprite we can customise SVG attributes like ‘fill’ according to our need as with Inline SVG. It also doesn’t make network call for each SVG required as everything is already loaded on first load. Also size of SVG sprite is much smaller than each SVG individually combined. In my testing for a sample of 11 SVGs, 35256 bytes were loaded when using each SVG externally, while only 28996 bytes were loaded when using sprite. Moreover, this sprite file is also cached by browser (in a scenario where we reload the page and and require the file again).
Con of normal PNG/JPEG repeats here — We are loading even those SVGs which we don’t require on our first fold screen, in our first network call.
Now the surprise, all issues related to separate network calls were solved in HTTP 1.1 protocol, as 1.1 supports parallel network calls(6 requests at a time), and it won’t choke the server like it does in 1.0. Moreover, HTTP 2.0 doesn’t have any limit for parallel network calls. Hence, this problem isn’t one of our concerns as we at housing.com are supporting HTTP 2.0 protocol.
We didn’t go with JPEG/PNG image or their sprites due to their lack of scalability.
We were in past using icon fonts, as it’s cons weren’t much of a concern earlier, but now there is a need of multi-color icons and hence we can’t continue with icon fonts for the same. Hence, only option is to go with SVG.
I didn’t want to clutter our JSX code with irrelevant SVG code by going inline. Additionally, repeating the same code for multiple places in case same icon is showing multiple times, increases file sizes unnecessarily.
I wanted to use SVG by importing it as a React Component, but we don’t use create-react-app, and adding support of SVGR was giving some trouble as we already have some image compression support for SVG in place and these two weren’t able to work together. Hence, had to drop this approach.
External SVG seemed a better option as this also helps to load only those SVGs which are currently required, whereas sprite would have included them all at once. Though with this we won’t be able to change fill color properties of SVG using CSS.
Hence, we will now be using a mix of external and SVG sprite depending on the use case. eg. if we want to change color or want completely different SVG icon on hover/click for few icons, we will use sprite only for those ones to avoid lag in slow networks, otherwise we will be using external SVGs for the rest of them.