Thomas Fuchs
Hi, I'm Thomas Fuchs. I'm the author of Zepto.js, of script.aculo.us, and I'm a Ruby on Rails core alumnus. With Amy Hoy I'm building cheerful software, like Freckle Time Tracking and Every Time Zone and write books like Retinafy.me.
   Want me to speak at your conference? Contact me!

Why and how I ditched icon fonts in favor of inline SVG

October 31st, 2014

Webfonts are the new hotness, and icon fonts even more so. There’s plenty to choose from, and Google just released an icon font that’s based on the new Android L. Icon nirvana reached?

Not really. Icon fonts have a few nasty problems that icon font makers rarely make you aware of. Here’s a few of these problems:

If you serve icons from a service, that services might randomly go down. I’ve seen this over and over again with big and small websites.

You can’t easily edit icons, and it’s hard to see what’s been updated in source code control. Webfont files are for the most part binary and changing one icon normally means the whole resulting font file is different.

There’s blurry rendering on certain browsers. Some CSS properties have to be abused (like -webkit-font-smoothing: antialiased;) to make the icons look good. This doesn’t work in some browsers and in some browser versions.

Most importantly, when icons are in the same CSS block, or when centered in an odd-width container, you get off by half-a-pixel blurryness, which can’t be corrected. This happens on all browsers, even on the latest and greatest Firefox, Chrome and Safari (even though Safari is doing better than IE, Firefox and Chrome, I’ve seen it happen on Safari as well).

Here’s some screenshots from GitHub on IE 10, showing this issue (it also happens with other browsers, like Chrome):

ie10

In Freckle Time Tracking we had a clever setup with SVG source files (on a precise 16×16 grid) and using fontcustom to compile fonts and doing some post-processing of the generated CSS with sed (most to remove unwanted font variants, as you only need woff in recent-ish browsers).

We also required to add some special headers when serving these font files so that our CDN (Amazon CloudFront) would correctly serve them.

All of this worked, but… blurryness alarm!

Even-width container:
Screen Shot 2014-10-09 at 9.05.28 PM

Odd-width container:
Screen Shot 2014-10-09 at 9.05.20 PM

Back to square one

Let’s take a step back and ask—what do you need from an icon? Why do icon fonts appear to be a good solution? And how can the problems be fixed?

  1. Icons need to be rendered pristinely and in focus.
  2. They should ideally be rendered the same or nearly the same on all browsers you support.
  3. You should be able to change the color and ideally other (CSS) properties.
  4. The same icon should look great on normal and retina screens; ideally you can substitute a different version for retina if you so fancy.
  5. It would be great if the files are text, so they work great with source code management. (Did you know that GitHub does graphical SVG diffs?)

Inline SVG to the rescue…

Here’s our current workflow:

  • We’ve a bunch of SVG files in a folder, which are our icons. All of them are on a 16×16 pixel grid.
  • There’s shell script that optimizes the SVG, and then generates a Rails helper and a JavaScript helper, as well as copies the optimized SVG files into /public/images:
    #!/bin/sh
    # This requires "imagemin", install via:
    # npm install --global imagemin
     
    echo "Cleaning up..."
    rm -rf build
    mkdir build
     
    echo "Optimizing SVG..."
    imagemin *.svg ../public/images/icons
     
    ruby ./generate_helper.rb
    
  • For the optimization, we use imagemin, which will remove unnecessary stuff from the source SVG files (these are all saved in Adobe Illustrator, which likes to be on the verbose side when it comes to saving SVG).
  • There’s a Ruby script that reads the optimized SVG files one by one and generates a Rails helper file with icon_xxxxx methods. These methods can be called from any view to insert the desired icon as inline SVG:
    #!/usr/bin/env ruby
    require 'json'
     
    FILENAME = "../app/helpers/icon_helper.rb"
    JAVASCRIPT_HELPER = "../public/js/icons/icons.js"
     
    puts "Generating helper #{FILENAME}"
    File.open(FILENAME, 'w') do |helper|
      helper.write "module IconHelper\n\n"
     
      Dir.glob(File.join("..","public","images","icons","*.svg")).each do |icon|
        svg = File.read(icon).delete("\n").delete("\r").delete("\t")
        name = icon.split('/').last.tr('-','_').gsub(/\.svg$/,'')
        puts "#{name}: #{svg.size} bytes"
     
        helper.write "  def icon_#{name}\n"
        helper.write "    '#{svg}'\n"
        helper.write "  end\n\n"
      end
     
      helper.write "end"
    end
     
    puts "Generating JavaScript helper"
    File.open(JAVASCRIPT_HELPER, 'w') do |helper|
      Dir.glob(File.join("..","public","images","icons","*.svg")).each do |icon|
        svg = File.read(icon).delete("\n").delete("\r").delete("\t")
        name = icon.split('/').last.tr('-','_').gsub(/\.svg$/,'')
        
        # we only need a few specific icons in JavaScript, so skip those we don't want
        next unless name =~ /^cursor/ || name =~ /^arrow_right/
        
        puts "(JavaScript) #{name}: #{svg.size} bytes"
     
        helper.write "window.__icon_#{name} = #{svg.to_json};\n"
      end
    end
    

    Calling on icon_xxxx generates inline SVG as shown below:
    Screen Shot 2014-10-31 at 12.30.37 PM

  • The Ruby script also generates a JavaScript helper file that’s similar. We only include icons in it that we need from the few places we generate HTML in JavaScript, like our calendar widget.
  • Last but not least, the optimized files are copied into /public/images so you can use
    them for special cases when you don’t want inline SVG but a normal image tag or a background image.
  • To set the color of an icon in CSS, just add a fill: #abcdef; CSS property.

Boom—problem solved. This works great on all browsers, and you don’t get any of the rendering issues that you have with icon fonts.

Actually there’s several unexpected advantages:

  • It’s easier to use icons if you inject JavaScript into 3rd-party websites. We use this for the pink support widget that’s on the bottom right of Freckle’s landing page.
  • If you want to do more advanced animations, like morphing an icon into an other icon, it’s pretty easy to do as you can manipulate inline SVG easily with JavaScript (and you can use CSS animations as well!)
  • When you copy+paste text, the SVG is ignored so you don’t get weird extra characters in the pasted text.
  • There’s no weird “flash of unstyled text”, neither is there empty spaces that are only rendered when an icon font was fully loaded.

The only downside is perhaps that your HTML will be slightly larger than it would be with an icon font, which might result in slighter longer loading and rendering times. This is offset by not having to load an icon font in the first place, however, and in my tests has not been a problem at all. A possible way to reduce the HTML size is to set data-icon attributes on elements and have JavaScript insert the inline SVG (I’d say you’re probably better off with directly rendering tho, as running JavaScript isn’t “free” either).

Enjoy the SVG goodness!