Maintainable, retina-optimized SVG icons

There are several options for using icons in your web project. This tutorial describes the benefit of using SVGs (Scalable Vector Graphics) prior to other techniques like CSS-Sprites or Icon-Fonts to deliver retina-optimized icons with FIT.

Why should I use SVG Icons?
  • SVGs are (contrary to pixel graphics like PNG) “scalable”. That means they’re resizable up and down without losing quality. Thus, SVGs are extra sharp on retina displays while retaining a small file size.
  • Since SVG icons are treated as single files, adding, updating or removing icons in or from your icon collection is easy. For example you do not have to recompile an updated icon font or create a new sprite image.
  • With SVG you can have multi-color icons which is not possible with icon fonts.
  • Since SVG code is human readable, changing icons properties like the color is as easy as changing attributes in a HTML file.
  • A note on using icon fonts: Browsers consider icon fonts as text. So the icons are anti-aliased, which can lead to icons not being as sharp as you might expect.
Are there any disadvantages to SVG Icons?

The greatest disadvantage of SVGs is the lacking support for older browsers (IE 8- and Android 2.3-.), because implementing a fallback would be too bigger a pain to be worth it. (Maintaining a PNG copy, inserting an additional element to display PNG version, hiding SVG element… it’s weighty).

Fortunately we have FIT: FIT creates PNGs from our SVG files for unsupported browsers automatically. Therefore there is no hassle with creating and embedding fallback images. Furthermore, FIT offers the image-inlining feature, which allows us to inline images as data URIs in our CSS file. More on that below.

Prepare your SVGs

When you’re creating the SVG files for your project there are several things to keep in mind that will make the future usage of your icons easier and much more efficient:

  • Use the same document size (width and height) for all your files, for example 60x60 pixels.
  • Center the icon symbol horizontally and vertically within the document.
  • Use as less margin around the symbol as possible, but make sure symbols have correct proportions to each other.
  • Use transparent backgrounds. This gives you more flexibility on styling your icons with CSS later on.

Following the rules mentioned above your icons should look like this:

SVG Best-Practices

Here is how your icons should not look like:

SVG Bad-Practices

To avoid issues in some older browsers that support SVG there are some code aspects to note. Therefore you should always inspect the source code of your SVG files and make sure that:

  • each file has the attributes width, height and viewBox defined,
  • transformations or other modifications are not applied with the use attribute (The use attribute will also lead to problems when creating PNG fallback images for browsers without SVG support, see image scaling)
  • an XML file type declaration is defined,
  • the SVG source code is cleaned up without duplicate path definitions or other unnecessary tags.
  • any local <styles> definitions are converted to path attributes (e.g. <path id="my-path" fill="#024f95" ...> instead of <style> .my-path{ fill:#024f95; } </style>

The following SVG source code was generated by Adobe Illustrator. It has several problems:

<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
 <defs>
  <path d="m19.8,26.1c-0.2,0.1 -0.2,0.3 -0.2,0.5c0,0.2 0.1,0.4 0.2,0.5l9.8,10.3c0.1,0.2 0.3,0.2 0.5,0.2c0.2,0 0.4,-0.1 0.5,-0.2l9.8,-10.3c0.1,-0.2 0.2,-0.3 0.2,-0.5c0,-0.2 -0.1,-0.4 -0.2,-0.5l-1.2,-1.1c-0.1,-0.2 -0.3,-0.2 -0.5,-0.2c-0.2,0 -0.4,0.1 -0.5,0.2l-8.2,8.6l-8.3,-8.6c-0.1,-0.2 -0.3,-0.2 -0.5,-0.2c-0.2,0 -0.3,0.1 -0.5,0.2l-0.9,1.1z" id="SVGID_1_"/>
 </defs>
 <clipPath id="SVGID_2_">
  <use id="svg_1" xlink:href="#SVGID_1_"/>
 </clipPath>
 <g transform="matrix(1,0,0,-1,0,58.4)">
  <title>Layer 1</title>
  <use x="0" y="-2" id="svg_2" fill="#000000" clip-rule="evenodd" fill-rule="evenodd" xlink:href="#SVGID_1_"/>
 </g>
</svg>

The version below solves these problems. It has been cleaned up, an XML file declaration has been added, the use definition has been merged right into the path-attribute and the attributes width, height and viewBox have been defined:

<?xml version="1.0" encoding="utf-8"?>
<svg width="60" height="60" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
  <g>
    <path fill-rule="evenodd" fill="#000000" d="m19.7,35.1c-0.2,-0.1 -0.2,-0.3 -0.2,-0.5c0,-0.2 0.1,-0.4 0.2,-0.5l9.8,-10.3c0.1,-0.2 0.3,-0.2 0.5,-0.2c0.2,0 0.4,0.1 0.5,0.2l9.8,10.3c0.1,0.2 0.2,0.3 0.2,0.5c0,0.2 -0.1,0.4 -0.2,0.5l-1.2,1.1c-0.1,0.2 -0.3,0.2 -0.5,0.2c-0.2,0 -0.4,-0.1 -0.5,-0.2l-8.2,-8.6l-8.3,8.6c-0.1,0.2 -0.3,0.2 -0.5,0.2c-0.2,0 -0.3,-0.1 -0.5,-0.2l-0.9,-1.1z"/>
  </g>
</svg>

Project setup

Follow the first tutorial to create a basic site structure. Then create a folder for your icons and copy all SVG files into it:

mkdir -p public/img/icons

Next we need to add a configuration file into our project, which tells FIT to at least enable the features image-scaling and image-inlining:

$EDITOR conf/config.xml
<config>
  <ress>
    <!-- Enable image-scaling to allow PNG fallback creation for browsers without SVG support -->
    <image-scaling />
  </ress>
  <acceleration>
    <!-- Enable image-inlining to serve all of your icon files as data-urls inline in our CSS file to reduce the number of network requests sent to the server -->
    <image-inlining />
  </acceleration>
</config>

Write your icon defintions in CSS

Now it’s time to write the final icon definitions. Therefore we first create a new CSS file:

mkdir public/css
$EDITOR public/css/styles.css

Our CSS code starts with the Adaptation Instruction ai-inline="true" which tells FIT to inline all following background-images as base64 encoded data URIs. Without image inlining each single icon file would cause a separate network request, which would be bad from a performance point of view. You can read more about image inlining in FIT here.

The following .ico CSS block then defines common styles applied to all icons. To make these styles work properly it is important to follow the rules mentioned above. After the common styles block we need to add a specific code block for each of our icons, that contains the background-image definition.


/* tell FIT to inline all following background-images */
/* ai-inline="true" */

.ico { display: inline-block; vertical-align: middle; background-repeat: no-repeat; background-position: center; background-size: 34px; width: 20px; height: 20px; /* If your icons should trigger click events and you're not using inline onclick handlers for that, make sure to set the cursor to 'pointer' to enable the event delegation in safari. See http://www.quirksmode.org/blog/archives/2010/10/click_event_del_1.html */ cursor: pointer; }

.ico-cart { background-image: url('../img/icons/cart.svg'); }

.ico-locator { background-image: url('../img/icons/locator.svg'); }

.ico-star { background-image: url('../img/icons/star.svg'); /* You can also fine-tune the icon properties, e.g. the size, here if needed */ background-size: 36px; }

.ico-twitter { background-image: url('../img/icons/twitter.svg'); }

/* Define more icons here... */

/* switch off image-inlining again */
/* ai-inline="false" */

Use your icons

Once all of your icons have been defined in CSS, they’re ready to use. Therefore we first need to include the styles.css in our main HTML file. Using an icon then can be done by simply adding the icon common style class ico and an icon specific class, e.g. ico-cart, to your HTML tags.

$EDITOR public/index.html
<html>

  <head>
    <!-- include our icon styles -->
      <link href="fit://site/public/css/styles.css" rel="stylesheet" type="text/css">
  </head>

  <h1>Hello World!</h1>

  <!-- Sample icon usage: -->

  <header>
    <a href="#" class="ico ico-cart"></a>
    <a href="#" class="ico ico-locator"></a>
  </header>

  <ul class="rating-5-of-5">
    <li class="ico ico-star">
    <li class="ico ico-star">
    <li class="ico ico-star">
    <li class="ico ico-star">
    <li class="ico ico-star">
  </ul>

</html>

Using animated SVGs

It is even possible to use animated SVGs. However, some restrictions apply: Since the browser support for animated SVGs is not as broad as that for normal SVGs and FIT cannot create fallback images for animations (e.g. animated GIFs), we need to provide fallback images by ourselves.

The following CSS code demonstrates this fallback mechanism for a sample loading animation.

.loading-spinner { position: fixed; z-index: 999; top: 50%; left: 50%; width: 60px; height: 30px; margin-top: -15px; margin-left: -30px; border-radius: 5px; background-color: rgba(207, 207, 207, .5); background-repeat: no-repeat; background-position: center; background-size: 100%; }

/* For browsers without general SVG and SVG SMIL support (mainly Android 2.3- and all versions of Internet Explorer) we need to manually serve an animted GIF instead of a SVG. Furthermore we need to instruct FIT to not use Image Scaling for animated GIFs. */

/* ai-inline="true" ai-scale="false" */

.loading-spinner { /* ai-if="not(client/image/svg) or contains(client/name, 'Internet Explorer')" */ background-image: url('../img/loading-animation-fallback.gif'); /* ai-else */ background-image: url('../img/loading-animation.svg'); /* ai-endif */ }

/* ai-scale="true" ai-inline="false" */