Creating an SVG icon workflow

By using Gulp as a build tool and providing fallbacks for older browsers.

By Luke Whitehouse on

CSS

Tutorial

A tutorial on how you can leverage all the fantastic benefits of an SVG based icon system whilst still providing support for those pesky older browsers.

The following post is a tutorial taken from the archived blog on my portfolio. If you're new to Assortment then welcome! This is my personal web development blog where I share practical tutorials and articles to help developers of all skill levels.

Lets get one thing straight; SVGs are pretty awesome. You only have to take a quick trip around the Web to see the amazing possibilities it offers. One of these awesome features is its ability to create a pretty robust icon system for you. It's perfect and has everything you desire; you can scale them, stretch them, colour them and even animate them.

However, there's a catch. It's not supported in browsers (<IE8 and some older versions of Safari and Android). Life sucks, I know... but there's hope!

Before we get into how we can cope with this, lets take a step back and explain what this icon workflow provides and why it may be of use to you.

Pros

  • Simplicity: Export your icons from Illustrator or a website like Icomoon and stick them in a folder.
  • Inline SVG: You'll have access to edit the SVGs in the DOM. What does this mean? You can use fill: red in your CSS and it'll bloody work!
  • Support: IE8 and up, for all you unfortunate souls (me included). Its also very simple to take out the fallback when its no longer required.
  • Automatic Support: Automatically generated PNG's on the fly. Include the files are you're set.

On a side note, Chris Coyier's talk on SVGs is a must watch and he also has a blog post all about SVG icon systems. I'd encourage anyone wanting to get into the land of SVG and Icon systems to give them both a look.

View this workflow on Github

Setting up the project

OK, lets get started on this abomination.

I'm going to be writing a lot of this in Command Line which some of you may not be so comfortable with... but don't run for the hills just yet. I'll break everything down into bite-sized chunks that'll hopefully be easier to digest. So, join me on this rollercoaster ride and let me explain the madness that rises in my noggin'. Who knows, maybe it'll be of some use to you?


Open up your Terminal.app/iTerm (I'd recommend downloading the latter) and create a folder you'll use for this project, then navigate to it.

$ mkdir ~/sites/svg-icon-workflow
$ cd ~/sites/svg-icon-workflow

As we'll be using Gulp to help automate a lot of our workflow, we need to initiate a new NPM (Node Package Manager) project.

$ npm init

And install all the required NPM packages, like so:

$ npm install —save-dev gulp gulp-svg-symbols gulp-svg2png

Done that? Awesome, now you should have a package.json within your project that looks similar to this:

{
  "name": "svg-icon-workflow",
  "version": "0.0.1",
  "description": "An SVG icon workflow utilising Gulp as a build tool and providing fallbacks for older browsers",
  "repository": {
    "type": "git",
    "url": "https://github.com/lukewhitehouse/svg-icon-workflow"
  },
  "author": "Luke Whitehouse",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/lukewhitehouse/svg-icon-workflow/issues"
  },
  "homepage": "https://github.com/lukewhitehouse/svg-icon-workflow",
  "devDependencies": {
    "gulp": "^3.9.0",
    "gulp-svg-symbols": "^0.3.1",
    "gulp-svg2png": "^0.3.0"
  }
}

There might be a few other bits and bobs in there, but as long as you have the above then you're good to go. Also, depending on when you're viewing this article could mean that the packages within the devDependencies section may have updated versions. Next we'll need to create our folder structure. Do this however you feel like, but using this as a basic:

assets/
  app/
    icons/
    // Rest of your folders – scss, js, etc.
  dist/
    icons/
      png/
    // Rest of your folders – css, js, etc.

Gulping and stuff

Now that we have our project setup, lets create the tasks that'll automate all our woes and worries. Create a gulpfile.js in the root of your project

$ touch gulpfile.js

We now need to require all the packages we installed via npm at the start. At the top of your file write the following...

var gulp     = require('gulp'),
  svg2png    = require('gulp-svg2png'),
  svgSymbols = require('gulp-svg-symbols');

If you're new to gulp or build tools, I suggest you take a look at some of these tutorials to get you started. What I'm basically doing here is referencing the packages I need and assigning it to a variable (so that I don't have to write require(blabla) every time I want to use utilise a package.

PNG generator task

Next, lets create our PNG generator task. Underneath your require statements, write the following.

gulp.task('svg2png', function () {
  // steps and stuff
});

What have we done here? Well we've registered a gulp task called svg2png. This allows us to go into Command Line and type gulp svg2png, which will run any code within the annonymous function

Note: For info on anonymous functions and other syntax related do-dats, I'd recommend you give Eloquent Javascript a read. The online version is free.

Lets make our new task do something. Thinking logically about this, before we run the PNG generator, we need to define where the SVG files are located. Lets do that using the src() method. This'll sit within our anonymous function.

return gulp.src( 'assets/app/icons/**/*.svg' )

Now our task knows we want to use any .svg file within our assets/app/icons folder.

Note: If you ever see reference to * or **/* then thats called globbing. In this case it 'compiles' to "Hey machine, I'm looking for any files with an extension of .svg within my designated folder, no matter if they're in sub directories or not".

Next we want to run our svg2png package. On a new line, add the following

.pipe( svg2png() )

Now we need to tell our task where we want any response (new files in this case) from our task to be saved. Lets assign any response to our assets/dist/icons folder.

.pipe( gulp.dest( 'assets/dist/icons/png/' ) )

If you followed along you should now have a task that looks similar to the one below.

/**
* SVG -> PNG fallback task
*/

// Create new task
gulp.task('svg2png', function () {

  // Define source files
  return gulp.src( 'assets/app/icons/**/*.svg' )

    // Run the svg2png npm module on these source files
    .pipe( svg2png() )

    // Define where the response is distributed to
    .pipe( gulp.dest( 'assets/dist/icons/png/' ) )
});

Here's a quick breakdown on what we've accomplished so far. First, we create our gulp task, which we can use by writing gulp svg2png into the terminal. Then, we've told our task where to look for our .svg files; followed by initialising our svg2png package on those files and sending the response (the new png files) to our assets/dist/icons folder.

Symbols bundler task

OK we've got our PNG generator task, let's create one that'll work with our SVG files themselves now.

This task will take a bunch of separate SVGs and bundle them into one single SVG file through the power of SVG symbols.

Lets get started ...

gulp.task('sprites', ['svg2png'], function () {
  // steps and stuff
});

Like with our svg2png task, we've defined our new task but within the squared brackets (array) is a list of any tasks you want to be run before this one starts. So in this case, if you run gulp sprites, gulp svg2png will be run automatically beforehand. Moving on, once again we'll define our source files which in this case are any icons within our assets/app/icons/ folder.

return gulp.src( 'assets/app/icons/**/*.svg' )

Now to run the svgSymbols package. This package allows us to configure the output a little through an object (the curly brackets).

.pipe(
  svgSymbols({
    className: '.icon--%f'
  })
)

Thats it, we've now setup our Gulp tasks. Theres a few other tasks you'd want in a full Gulp build, for example watch and default tasks; but you can look into those elsewhere.

Using SVGs

Lets test our tasks are working now and look into how we'll use SVGs within our projects. Go ahead and create some SVGs in your assets/app/icons folder. You can export custom ones from Illustrator or drop them in from a system such as Icomoon or Fontello.

Note: As the PNG files are generated from your SVGs, so whatever the original size of your SVG is will be the size of your new PNG. As a tip when dealing with unsupported browsers, I'd recommend sizing your SVGs to be the size you'll be using them for. Try to come up with some set sizes to help with consistency, for example:

  • Default = 18x18
  • Small = 14x14
  • Large = 24x24
  • X-Large = 42x42

OK, now you've got some icons ready, lets run our sprites task. Within your Command Line, run...

$ gulp sprites

You should now find that:

  • All your icons have been merged into a file at assets/dist/icons/svg-symbols.svg
  • Along with classes for each icon within assets/dist/icons/svg-symbols.css
  • PNGs have been generated within assets/dist/icons/png

All we need to do now is include the svg-symbols.svg within your HTML file. You could copy and paste this if you really wanted, but that'd mean c&p everytime you made a change to your icons and ran the gulp sprites task. Instead, I'd recommend you use a server-side include, for example, in PHP I'd do

<?php include("assets/dist/icons/svg-symbols.svg"); ?>

This must be just after the opening tag for < Chrome 37. You can read more on that here.

We're now ready to use our SVGs and tryout our cool little workflow-in-progress. Within your HTML file, write the following:

<svg viewBox="0 0 100 100" role="presentation">
  <use xlink:href="#"></use>
</svg>

Where the <nameofsvg></nameofsvg>is the name of an SVG you have within your system. For example, if I have an SVG called arrow-right.svg, I'd write the following:

<svg viewBox="0 0 100 100" role="presentation">:
  <use xlink:href="#arrow-right"></use>
</svg>

Note: Theres a lot of considerations you'll need to make with regards to making your SVGs accessible. I'd recommend you give Leonie Watson's post over on Sitepoint a read.

If you now view your element in the browser, you'll see it working. Try adding some some styling to it. fill: red; for starters. Maybe even a transform: rotate(160deg);. Its like magic.

Fallback automation

OK, so you now have an SVG workflow for modern browsers and we also have PNG files for each SVG. The only question that remains is how do we link the two? Well, I've already done that for you by creating a small script that you can add into your JS file. You'll want to add this file to the ending tag, or make sure that it loads after your SVGs.

/**
* Test for Inline SVG support
*/
function supportsSvg() {
  var div = document.createElement('div');
  div.innerHTML = '<svg/>';
  return (div.firstChild && div.firstChild.namespaceURI) == 'http://www.w3.org/2000/svg';
};

/**
* iconsFallback - Transform SVGs to PNGs when not supported
*/
function iconsFallback() {

  // If browser doesn't support Inline SVG
  if ( !supportsSvg() ) {

    // Get all SVGs on the page and how many there are
    var svgs = document.getElementsByTagName("svg"),
      svgL = svgs.length;

    // Loop through all SVGs on the page
    while( svgL-- ) {

      // If SVG isn't the first one, continue ...
      if(svgL > 0) {

        // Get title attribute of SVG
        var svgTitle = svgs[svgL].getAttribute("title");

        // Get all  elements from each SVG
        var uses = svgs[svgL].getElementsByTagName("use"),
          usesL = uses.length;

        // Loop through all  elements within an SVG
        while( usesL-- ) {

          // Get the 'xlink:href' attributes
          var svgId = uses[usesL].getAttribute("xlink:href");

          // Remove first character from variable (This removes the #)
          svgId = svgId.substring(1, svgId.length);

          // Create New Image
          var newImg = document.createElement("img");

          // Assign src attribute
          newImg.src = "assets/dist/icons/png/" + svgId + ".png";

          // Assign alt attribute
          newImg.alt = svgTitle ? svgTitle : '';

          // Insert new element straight after the SVG in question
          svgs[svgL].parentNode.insertBefore(newImg, svgs[svgL].nextSibling);
        }

        // Remove all SVG nodes
        svgs[svgL].parentNode.removeChild(svgs[svgL]);
      }
    }
  }
};

// Call fallback function
iconsFallback();

Concluding thoughts

While theres quite a few steps to this process, I hope you'll agree that the result is worth it. I'm sure there are some better ways of accomplishing my goals; especially when it comes to the fallback script so please do leave feedback if you have any. Hope this helps a few of you out there.

View this workflow on Github

This post is a tutorial taken from the archived blog on my portfolio. If you're new to Assortment then welcome! This is my personal web development blog where I share practical tutorials and articles to help developers of all skill levels.

Until next time 

Follow us on Twitter, Facebook or Github.
© Copyright 2021 Assortment.
Created and maintained by Luke Whitehouse.