Lazy Loading SVG Icons using Web Components
Tags: codepen, webcomponents, icon, svg
Published: 2021-09-19 22:15:27

{% codepen https://codepen.io/john-theo/pen/LYLddpd %}

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](https://www.flaticon.com/packs/library) 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](https://fontawesome.com/), [Google Font Icons](https://fonts.google.com/icons) and many others are taking this approach.

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

- [Icon fonts vs SVG - which is the best option?](https://www.creativebloq.com/web-design/icon-fonts-vs-svg-101413211)
- [Inline SVG or Icon Fonts: Which One to Use](https://betterprogramming.pub/inline-svg-or-icon-fonts-which-one-to-use-77c0daf1c527)
- [Inline SVG vs Icon Fonts [CAGEMATCH]](https://css-tricks.com/icon-fonts-vs-svg/)
- [Icon Fonts vs SVG – Clash of the Icons](https://www.lambdatest.com/blog/its-2019-lets-end-the-debate-on-icon-fonts-vs-svg-icons/)

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](https://i2.wp.com/css-tricks.com/wp-content/uploads/2014/04/svg-font.png)

> This image is originally published [here](https://css-tricks.com/icon-fonts-vs-svg/)

### Cons

- Not easy to cache (See [this section]((https://betterprogramming.pub/inline-svg-or-icon-fonts-which-one-to-use-77c0daf1c527#b912)))
- 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.

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

<span class="material-icons-outlined"></span>
```

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](https://github.com/John-Theo/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](https://akaricons.com/), 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

```html
<img src="foo.svg" alt="bar">
```

#### 2. Use SVG as CSS background

```css
.container {
  background: url(foo.svg);
}
```

#### 3. Use the `<object>` tag

```html
<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>
```

#### 4. Use static inline SVG

```html
<svg> ... </svg>
```

#### 5. Leveraging the `<use>` tag

> The [`<use>`](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/use) tag can be used to refer an external SVG (similar to the `src` attribute in the `<img>` tag).

```html
<svg viewBox="0 0 100 100">
   <use xlink:href="foo.svg#bar"></use>
</svg>
```

#### 6. Insert SVG content as innerHTML by js

```js
const foo = document.querySelector('foo');
foo.innerHTML = '<svg> ... </svg>'
``` 

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](https://github.com/John-Theo/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](https://github.com/John-Theo/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](https://developer.mozilla.org/en-US/docs/Web/Web_Components) for a while and is willing to try [StencilJS](https://stenciljs.com/), 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](https://www.npmjs.com/package/akar-icons-web-components) 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](https://www.npmjs.com/package/akar-icons-web-components) loads only **~10KB** of data, compared to the original [integrated SVG of AkarIcons](https://unpkg.com/akar-icons-fonts@1.0.8/src/fonts/akar-icons.svg) **~200KB** gzipped.

### Accessibility

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

<akar-icon name="bicycle"> </akar-icon>
```

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](https://www.npmjs.com/package/akar-icons-web-components), SVGs are loaded using `fetch` from the original [AkarIcons github repo](https://github.com/artcoholic/akar-icons-app/tree/main/src/svg). 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](https://www.npmjs.com/package/akar-icons-web-components) further extended the customization capability of [AkarIcons](https://akaricons.com/), 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m69k7ucfqg6vo7er7wdv.gif)

## Everything Said

Finally, huge thanks to [AkarIcons](https://akaricons.com/), this project won't come to life without you. And hope you enjoyed reading this article!