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:
- Use a similar icon (and look away from the not-so-perfect creature);
- 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);
- 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:
- Icon fonts vs SVG - which is the best option?
- Inline SVG or Icon Fonts: Which One to Use
- Inline SVG vs Icon Fonts [CAGEMATCH]
- Icon Fonts vs SVG – Clash of the 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
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>
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">
2. Use SVG as CSS background
.container {
background: url(foo.svg);
}
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>
4. Use static inline SVG
<svg> ... </svg>
5. Leveraging the <use>
tag
The
<use>
tag can be used to refer an external SVG (similar to thesrc
attribute in the<img>
tag).
<svg viewBox="0 0 100 100">
<use xlink:href="foo.svg#bar"></use>
</svg>
6. Insert SVG content as innerHTML by 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. 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>
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" |
Everything Said
Finally, huge thanks to AkarIcons, this project won't come to life without you. And hope you enjoyed reading this article!
Top comments (2)
Why fetch external SVG at all? You can let the Web Component create the SVG Client-Side: iconmeister.github.io
You can go smaller and faster with the iconmeister.github.io Web Component. Doing 7400+ icons
Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more