sketchboard blog

How to Use Inline SVG icons

Apr 23, 2019

Lonely Planet was one of the first who publicly explained their process of using SVG icons. The approach has changed a bit, but they are still using SVG images.

The benefit of using SVG icons is that graphics are drawn losslessly on every device and any screen size.

We have followed the Lonely Planet approach, but as time has passed, browsers have started to support the first approach differently where SVG icons are loaded using CSS. Our SVG sprite icons on Windows began to look a mess.

Icons are shown correctly on MacOS Icons are shown correctly on MacOS

Broken icons on Windows Broken icons on Windows

Since then Lonely Planet and several others like GitHub have updated their SVG sprite implementation to inline SVG icons.

We decided to take the opportunity to change our SVG icon sprite implementation to inline SVG icons to fix the broken icon problem. Now Windows show icons correctly and there is an added bonus that icon colors can be changed dynamically. The SVG file size is also reduced.

Our legacy SVG sprite implementation loaded SVG sprite file using CSS URL function. The approach didn’t give a possibility to use different colors for the icons. E.g., we toggle image colors depending on if an icon is active or not.

[class ^="menu-icon-"],[class *=" menu-icon"] {
  display: inline-block;
  background-image: url('../images/svg_icon_sprite_v17.svg');
  width: 43px;
  height: 18px;
  background-size: cover;
  vertical-align: middle;
}
.hand-active {
  background-position: 0 216px;
}
.hand-inactive {
  background-position: 0 198px;
}

Icon Active

Icon Inactive

When icons are loaded using CSS, we needed to have multiple versions of the same icon using alternate colors.

New inline SVG icons allow us to have only one single version of an icon and update color dynamically using CSS or JavaScript.

.hand-active {
  fill: white;
}

.hand-inactive {
  fill: green;
}

It is quite a bit of work when creating an SVG sprite file that is loaded using CSS. You need to place SVG icons correctly in the sprite file, update the position to the icon in the CSS file. In case you remove an image in the middle, all icon positions need to be updated after that.

Luckily the process is more straightforward with new tools. Svg-sprite generates an SVG symbol file that can be inlined into your web page. There is a gulp wrapper for that as well, gulp-svg-sprite.

No more hassle with updating icon position in the CSS file, just refer icons with their id.

<svg
  xmlns="http://www.w3.org/2000/svg"
  xmlns:xlink="http://www.w3.org/1999/xlink">

<symbol
  viewBox="0 0 100 100"
  id="context-menu--add-diagram"
  xmlns="http://www.w3.org/2000/svg">
  <path
    fill-opacity=".941"
    d="M0 23.88h27.16v52.241H0zM76.543 0H100v26.12H76.543zM76.543 36.94H100v26.12H76.543zM76.543 73.88H100V100H76.543z"></path>
  <path
    d="M75.309 13.06L25.926 49.63h51.852"
    fill="none"
    stroke-width="7.183"
    stroke-linecap="round"
    stroke-miterlimit="1"></path>
  <path
    d="M25.926 53.547l49.383 32.65"
    fill="none"
    stroke-width="7.183"
    stroke-linecap="round"></path>
</symbol>

<symbol
  viewBox="0 0 99.724 100"
  id="context-menu--conn-curve"
  xmlns="http://www.w3.org/2000/svg">
  <g
    fill="none"
    stroke-width="10.196">
    <path
      d="M71.771 50.084l20.98 24.051-22.26 22.26M5.19.194C1.797 89.358 92.385 73.939 92.385 73.939"></path>
  </g>
</symbol>

</svg>

Our Process to add new SVG icons

  • Use vector graphic tool to create your icon. Export the outcome as an SVG file. We use Inkscape and Affinity Designer for iPad.
  • Configure svg-sprite.js to create an SVG sprite file for you
  • Inline SVG icons into your HTML file
  • Configure colors and sizes with CSS

Inline SVG file into HTML file

You can embed your SVG file statically into your index.html file on the server side. That is probably the best way to do it if you don’t have too many icons.

There is one drawback if you have lots of SVG icons inlined statically into your HTML file, the browser doesn’t cache those, and you need to embed images on all pages that refer to the icons.

We decided to inline SVG icons dynamically. In this technique, the JavaScript file loads the SVG file and inserts SVG icons on runtime.

This approach utilizes browser cache. On the first time, this is slower, but next times are fast due to browser fetches from the local cache.

When the SVG sprite file needs to be changed, we append MD5 checksum to the file name. The unique version number allows us to use CDN in front of the SVG sprite file and later on service workers.

sb_icons_<md5_checksum>.svg

An example, how to insert an SVG file into an HTML file using TypeScript. The following approach is compatible with service worker JavaScript file, when sw.js checksum changes, the service worker gets installed again.

var body = document.querySelector('body')

var svgDiv = <HTMLDivElement>document.createElement('div')
svgDiv.id = 'inline-svg'
svgDiv.style.display = 'none'
body.appendChild(svgDiv)

class LoadSVG {
  constructor(svgFile: string) {
    var loadXML = new XMLHttpRequest;

    function handler() {
      if (loadXML.readyState == 4 && loadXML.status == 200) {
        svgDiv.innerHTML = svgDiv.innerHTML + loadXML.responseText
      }
    }
  
    loadXML.open("GET", svgFile, true);
    loadXML.onreadystatechange = handler;
    loadXML.send();
  }
}

function loadSVGasXML()
{
    var svgFiles = [
      // Local host on dev mode and CDN on production.
      `${_config.staticPrefix}/inline-svg/sb_icons_cb5f07c8d6542482816f7459451a59bf.svg`
    ]

      for (var i = 0; i < svgFiles.length; ++i) {
        let svgFile = svgFiles[i]

        new LoadSVG(svgFile)
      }
}

function init(evt: Event) {
  loadSVGasXML()
}

window.addEventListener('load', init, false)

Our Makefile target modifies the JavaScript file with the updated MD5 value.

  # Update svg MD5 value on JS file
  sed -i '' -E 's/sb_icons_.+.svg/$(SVG_FILE)/g' $(TARGET_JS_FILE)

Improvements

Next, we are looking into loading inline SVG icons using service worker. We’ll be updating this blog post after the change.