DEV Community

Cover image for Lazy Loading SVG Icons using Web Components
John Dope
John Dope

Posted on

Lazy Loading SVG Icons using Web Components

Icons can be a real pain in the ass, and for good reasons.

Dilemma

For developer whose desired icon isn't be included in the component library he/she is using, normally he/she has three options:

  1. Use a similar icon (and look away from the not-so-perfect creature);
  2. Download a SVG from a SVG icon library like FlatIcon and put it into the page (but the whole process may give you a headache);
  3. Bring another set of icon font into the project.

But wait! That icon does not resemble it's neighbors! Too much padding, different color, even the stroke width looks strange.

SVG Icons

Icons, whatever format they exist in the websites, are mostly originated from SVG files. Using SVGs as icons are no new topic, but icon fonts still seem to be the major trend. Font Awesome, Google Font Icons and many others are taking this approach.

There are many articles discussed about SVG icons vs icon fonts, like:

To summarize, the major pros and cons of SVG icons (compared to icon fonts) are:

Pros

  • Sharp in retina display (icon fonts are often anti-aliased)
  • Easy positioning (icon fonts use pseudo elements)
  • Can be multichromatic even be animated

SVG fonts won't be anti-aliased

This image is originally published here

Cons

  • Not easy to cache (See this section)
  • Lack of (ancient IE verisons) compatibility
  • Brings a lot of elements into the DOM tree

It's not hard to see that the pros of SVG icons are truly critical to make user experience superb, while the cons are, well, they can be dealt with in most cases.

So, why is icon fonts so popular?

A11y is !important

The most apparent benefit of using icon fonts maybe because it's ease to use. One <script> tag and you are good to go. However when using SVGs as icons, you need to prepare a bunch of SVG files in the public folder (or insert them directly into html as inline SVGs), creating numerous <svg> tag in the page, deal with the icon sizes, paddings and other stuff. Even before you know it, you typed "Font Awesome" in the search bar.

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons">

<span class="material-icons-outlined"></span>
Enter fullscreen mode Exit fullscreen mode

So is there a way to make SVG icons as accessible as icon fonts?

A Project of SVG Icons

I was coding a leisure project Tooolbar, which requires to use a bunch of icons. The aim of the project is to generate a nice-looking toolbar painlessly with several lines of JSON. I want to give the users full control of what icons they choose to put in the toolbar, and I don't want the project to be icon-library-dependent. So it leaves me no choice but to use SVG as icons. (Yes, I can use PNGs and JPGs, but even my granpa won't consider it :)

During the develop, I learned amazingly how customizable can a SVG be. Knowing it's size can be easily changed, I learned that if one give the value of "currentColor" to the stroke or fill attribute of a <path>, the color of the <path> will inherit from it's ancestor. And great thanks to AkarIcons, I learned that the stroke-width and even stroke-linecap and stroke-linejoin property can be controlled by css.

Method Matters

To place an SVG into the html document, there are several ways:

1. Use the img tag

<img src="foo.svg" alt="bar">
Enter fullscreen mode Exit fullscreen mode

2. Use SVG as CSS background

.container {
  background: url(foo.svg);
}
Enter fullscreen mode Exit fullscreen mode

3. Use the <object> tag

<object type="image/svg+xml" data="foo.svg">
  <!-- fallback -->
</object>

<!-- or -->

<object type="image/svg+xml" data="data:image/svg+xml;base64,[data]">
  <!-- fallback -->
</object>
Enter fullscreen mode Exit fullscreen mode

4. Use static inline SVG

<svg> ... </svg>
Enter fullscreen mode Exit fullscreen mode

5. Leveraging the <use> tag

The <use> tag can be used to refer an external SVG (similar to the src attribute in the <img> tag).

<svg viewBox="0 0 100 100">
   <use xlink:href="foo.svg#bar"></use>
</svg>
Enter fullscreen mode Exit fullscreen mode

6. Insert SVG content as innerHTML by js

const foo = document.querySelector('foo');
foo.innerHTML = '<svg> ... </svg>'
Enter fullscreen mode Exit fullscreen mode

The problem of the first three approaches is that you don’t get to control the innards of the SVG with CSS at all. Using the <use> tag allows outer color css property to be inherited by the elements with "currentColor" in the SVG content, but due to the whole <svg> is wrapped in a "shadow root", SVG <path>s ignore any other css property such as "stroke-linecap" or "stroke-width", etc. And it requires the root <svg> tag of the external file to have an id attribute, which is rather hard to meet in common SVGs downloaded from the web.

So in short, inline SVG is the best and only choice.

What Web Component has to Offer

I didn't intend to use any framework to build Tooolbar. But during my development, I found my code to be very much alike a component, be it React Component, Vue Component or Web Component. It takes properties, emits events, can be nested, and takes care of the content inside it's root element.

Another thing I noticed during developing Tooolbar is that if I can render some nested HTML using nested JSON full of key-value pairs, why don't write some semantic html tag directly?

So how about refactoring the whole project using some kind of compoent? I made a call with @awmleer , described the idea to him, he said he has been watching Web Components for a while and is willing to try StencilJS, a web component framework.

Web Components technology features a wide variety of characteristics aligned perfectly with my needs:

  • Natively supported custom HTML tag;
  • Framework independent, vanilla js and css can be used;
  • CSS within the namespace, everything inside the "shadow root" play their own game;
  • <template> and <slot> tags are a perfect solution to the nested structure.

React or Vue.js or many other frontend frameworks can do most of the jobs, but I am a lazy man, I only want to write my code once and hope it can be used in anywhere. And I'm so lazy that I don't want to write any wrappers.

Introducing Icons Made with Web Components

With pains and gains in mind, only several hours after the first phone call, here are the final result.

So what did we do?

Size

After bundling, the index.js with its loader included is only ~4KB gzipped, every icon is lazy-loaded and only adds ~0.6KB to the traffic. So for a normal page with ~10 icons, akar-icon-web-components loads only ~10KB of data, compared to the original integrated SVG of AkarIcons ~200KB gzipped.

Accessibility

<script src="https://unpkg.com/akar-icons-web-components" type="module"></script>

<akar-icon name="bicycle"> </akar-icon>
Enter fullscreen mode Exit fullscreen mode

With one line of <script> import, you can now have any beautiful AkarIcon on your page. No <i>, no <span>, but <akar-icon>, and it's supported natively in the browser. Furthermore, you can put it inside of your React or Vuejs project, it still works perfectly.

Cahce

One of the cons metioned with SVG icons is it's not cached perfectly. In akar-icon-web-components, SVGs are loaded using fetch from the original AkarIcons github repo. We use a simple map object to keep track of whether an icon has been fetched or not, so the same icon won't be fetched twice in the same page load process.

Customization

akar-icon-web-components further extended the customization capability of AkarIcons, making all following attributes customizable:

Attribute Type Css Var Default
size number --size 24
color string color "inherit"
stroke number --stroke 1
cap enum --cap "round"
join enum --join "round"

Banner GIF

Everything Said

Finally, huge thanks to AkarIcons, this project won't come to life without you. And hope you enjoyed reading this article!

Discussion (2)

Some comments have been hidden by the post's author - find out more