Hi, developer!

When making a layout from PSD, icons are often inserted in SVG format, and if not, we ask them from the designer. Earlier we used icon fonts, but recently saw the advantages of sprites and decided to try to play with them to place them into the development process. We like icon fonts, but they have a number of disadvantages (you can read CSSTricks on this topic CSSTricks). The experiment was successful, and that’s how we have organized the system.

Conditions

What we need from sprites:

  1. Flexible management of the size, colour and behaviour (hover, focus, etc.) of an icon
  2. Automation, minimum of manual work
  3. Loading only the necessary icons on the page
  4. Easy insertion of icons into page layout (I use pug for templating html)

Folder structure:

├── gulpfile.js                # gulpfile
└──assets                      # here we edit the files
    └── pug/                   # template engine html
    └── sass/                  # styles
    └── js/                    # scripts
    └── i/                     # pictures, here we will put the sprite
└──dist                        # here we get the finished project

You can read and learn more about how our build works. in the repository. To create a sprite, use gulp, namely:

  • gulp-svg-sprites - sprite creation
  • gulp-svgmin - SVG minification
  • gulp-cheerio - removal of unnecessary attributes from svg
  • gulp-replace - fixing some bugs, about it we will tell below

Let’s go

Install the plugins (we do this globally and then link):

npm install gulp-svg-sprites gulp-svgmin gulp-cheerio gulp-replace -g
npm link gulp-svg-sprites gulp-svgmin gulp-cheerio gulp-replace

Unveil the plugins in the gulpfile:

var svgSprite = require('gulp-svg-sprites'),
	svgmin = require('gulp-svgmin'),
	cheerio = require('gulp-cheerio'),
	replace = require('gulp-replace');

Cooking sprite

The first task is to create an html file with symbol tags.

gulp.task('svgSpriteBuild', function () {
	return gulp.src(assetsDir + 'i/icons/*.svg')
		// minify svg
		.pipe(svgmin({
			js2svg: {
				pretty: true
			}
		}))
		// remove all fill and style declarations in out shapes
		.pipe(cheerio({
			run: function ($) {
				$('[fill]').removeAttr('fill');
				$('[style]').removeAttr('style');
			},
			parserOptions: { xmlMode: true }
		}))
		// cheerio plugin create unnecessary string '>', so replace it.
		.pipe(replace('>', '>'))
		// build svg sprite
		.pipe(svgSprite({
				mode: "symbols",
				preview: false,
				selector: "icon-%f",
				svg: {
					symbols: 'symbol_sprite.html'
				}
			}
		))
		.pipe(gulp.dest(assetsDir + 'i/'));
});

Let’s see what is happening here in parts.

We point where we need to get the icons and minify them. The variable assetsDir is for convenience.

return gulp.src(assetsDir + 'i/icons/*.svg')
	// minify svg
	.pipe(svgmin({
		js2svg: {
			pretty: true
		}
	}))

Remove the style, fill attributes from the icons so that they do not interrupt the styles specified via css.

.pipe(cheerio({
	run: function ($) {
		$('[fill]').removeAttr('fill');
		$('[style]').removeAttr('style');
	},
	parserOptions: { xmlMode: true }
}))

However, this plugin has one bug - sometimes it converts the symbol ‘>’ to the encoding ‘>’.
This problem is solved by the following piece of task:

.pipe(replace('>', '>'))

Now we will make a sprite from the one we have got and put it in a folder:

.pipe(svgSprite({
		mode: "symbols",
		preview: false,
		selector: "icon-%f",
		svg: {
			symbols: 'symbol_sprite.html'
		}
	}
))
.pipe(gulp.dest(assetsDir + 'i/'));

symbol_sprite.html - is our sprite. Inside it will contain the following (for simplicity, a couple of icons is shown):

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="0" height="0"
     style="position:absolute">

    <symbol id="icon-burger" viewBox="0 0 66 64">
        <path fill-rule="evenodd" clip-rule="evenodd" d="M0 0h66v9H0V0zm0 27h66v9H0v-9zm0 27h66v9H0v-9z"/>
    </symbol>

    <symbol id="icon-check_round" viewBox="-0.501 -0.752 18 18">
        <path d="M8.355 0C3.748 0 0 3.748 0 8.355s3.748 8.355 8.355 8.355 8.355-3.748 8.355-8.355S12.962 0 8.355 0zm0 15.363c-3.865 0-7.01-3.144-7.01-7.01 0-3.864 3.145-7.007 7.01-7.007s7.01 3.144 7.01 7.01-3.146 7.007-7.01 7.007z"/>
        <path d="M11.018 5.69l-3.9 3.9L5.69 8.165c-.262-.263-.688-.263-.95 0-.264.263-.264.69 0 .952l1.9 1.903c.132.13.304.196.476.196s.344-.066.476-.197l4.376-4.378c.263-.263.263-.69 0-.952s-.69-.262-.952 0z"/>
    </symbol>

</svg>

Pinch styles

Now we need to make styles for our sprite (in this case the .scss file). We can specify this file in the gulp-svg-sprites plugin, but what a nuisance - it cannot be done with this setting:

mode: "symbols"

Let’s make a separate task for creating scss.

// create sass file for our sprite
gulp.task('svgSpriteSass', function () {
	return gulp.src(assetsDir + 'i/icons/*.svg')
		.pipe(svgSprite({
				preview: false,
				selector: "icon-%f",
				svg: {
					sprite: 'svg_sprite.html'
				},
				cssFile: '../sass/_svg_sprite.scss',
				templates: {
					css: require("fs").readFileSync(assetsDir + 'sass/templates/_sprite-template.scss', "utf-8")
				}
			}
		))
		.pipe(gulp.dest(assetsDir + 'i/'));
});

In the cssFile property we declare where to put the file on the scss (then we include it).
In the templatesproperty we declare where to get a template for it. Template code:

.icon {
	display: inline-block;
	height: 1em;
	width: 1em;
	fill: inherit;
	stroke: inherit;
}
{#svg}
.{name} {
	font-size:{height}px;
	width:({width}/{height})+em;
}
{/svg}

We get the _svg_sprite.scss as follows:

.icon {
	display: inline-block;
	height: 1em;
	width: 1em;
	fill: inherit;
	stroke: inherit;
}

.icon-burger {
	font-size:64px;
	width:(66/64)+em;
}

.icon-check_round {
	font-size:18px;
	width:(18/18)+em;
}

The compiled css will be like this:

.icon {
	display: inline-block;
	height: 1em;
	width: 1em;
	fill: inherit;
	stroke: inherit;
}

.icon-burger {
	font-size: 64px;
	width: 1.03125em;
}

.icon-check_round {
	font-size: 18px;
	width: 1em;
}

Please note that the sizes of the icons are expressed in em, which will allow us to manage them through font-size.
Make the final task, and run it:

gulp.task('svgSprite', ['svgSpriteBuild', 'svgSpriteSass']);

Connect to the page

So, we have got an html-file with icons and a scss-file with the design. Next, connect the file to the page using caching via localStorage. This process is well described in the article Caching SVG Sprite in localStorage.
Connect the js-file as follows:

;( function( window, document )
{
	'use strict';

	var file     = 'i/symbol_sprite.html',
		revision = 1;

	if( !document.createElementNS || !document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' ).createSVGRect )
		return true;

	var isLocalStorage = 'localStorage' in window && window[ 'localStorage' ] !== null,
		request,
		data,
		insertIT = function()
		{
			document.body.insertAdjacentHTML( 'afterbegin', data );
		},
		insert = function()
		{
			if( document.body ) insertIT();
			else document.addEventListener( 'DOMContentLoaded', insertIT );
		};

	if( isLocalStorage && localStorage.getItem( 'inlineSVGrev' ) == revision )
	{
		data = localStorage.getItem( 'inlineSVGdata' );
		if( data )
		{
			insert();
			return true;
		}
	}

	try
	{
		request = new XMLHttpRequest();
		request.open( 'GET', file, true );
		request.onload = function()
		{
			if( request.status >= 200 && request.status < 400 )
			{
				data = request.responseText;
				insert();
				if( isLocalStorage )
				{
					localStorage.setItem( 'inlineSVGdata',  data );
					localStorage.setItem( 'inlineSVGrev',   revision );
				}
			}
		}
		request.send();
	}
	catch( e ){}

}( window, document ) );

The file is connected and it is cached after the first boot. If you need to update the sprite in the js-file above, change the revision parameter to +1. We insert the icons through the mixin pug, because it is fast and convenient:

mixin icon(name,mod)
	- mod = mod || ''
	svg(class="icon icon-" + name + ' ' + mod)
		use(xlink:href="#icon-" + name)

Now, in order to embed an icon we fetch a mixin with its name:

+icon('check_round','red_mod')
+icon('burger','green_mod')

Final html:

<svg class="icon icon-check_round red_mod">
    <use xlink:href="#icon-check_round"></use>
</svg>
<svg class="icon icon-burger green_mod">
    <use xlink:href="#icon-burger"></use>
</svg>

Open the page in the browser:

For now, the sizes of the icons are in full size and have a standard colour. Let’s change this (not in the generated file, but in the main file):

.icon-burger {
	font-size:3rem;
	&.green_mod {
		fill:green;
	}

}
.icon-check_round {
	font-size:3rem;
	&.red_mod {
		fill: red;
	}
}

Result:

That’s it, we’ve got a working system for connecting icons via sprites, but there is one more thing.

Blurring

Unfortunately, not all designers make icons on a pixel grid. In this case, the icons will be “blurred”. If you export icons from the illustrator, you need to initiate the pixel grid and adjust the size and location of the icon to the pixel grid. If you work in ready-made svg-files, use the iconmoon service iconmoon to properly align them.

That’s all.