How do I use SVG sprites

  • Tutorial
Hi, developer!
When typesetting layouts from PSD, icons are often inserted in SVG format. And if not, I ask them from the designer. Previously, I used icon fonts, but recently I saw the advantages of sprites and decided to try to play with them and introduce them into the development process. I like icon fonts, but they have a number of drawbacks (read CSSTricks on this topic ). The experiment was a success, and this is how I organized the system.

Conditions


What I want to get from sprites:
  1. Flexible control over the size, color and behavior (hover, focus etc) of the icon
  2. Automation, minimum manual work
  3. Icon caching for good page loading speed
  4. Easy insertion of icons into the page layout (I use jade to template html)


My folder structure:
├── gulpfile.js                # gulpfile
└──assets                      # здесь редактируем файлы
    └── jade/                  # шаблонизатор html
    └── sass/                  # стили
    └── js/                    # скрипты
    └── i/                     # картинки, сюда мы и будем вставлять спрайт
└──dist                        # здесь получаем готовый проект


You can read more about how my assembly works in the repository .
To create a sprite, we use gulp, namely:
  • gulp-svg-sprites - create a sprite
  • gulp-svgmin - minification of SVG
  • gulp-cheerio - remove redundant attributes from svg
  • gulp-replace - fixing some bugs, more on that below


Go!


Install plugins (I 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

In gulpfile we declare plugins:
var svgSprite = require('gulp-svg-sprites'),
	svgmin = require('gulp-svgmin'),
	cheerio = require('gulp-cheerio'),
	replace = require('gulp-replace');


Cook 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 happens in parts here.
We say where we need to get the icons from and minify them. The variable assetsDir is for convenience.
return gulp.src(assetsDir + 'i/icons/*.svg')
	// minify svg
	.pipe(svgmin({
		js2svg: {
			pretty: true
		}
	}))

We remove the style and fill attributes from the icons so that they do not interrupt the styles specified through css.
.pipe(cheerio({
	run: function ($) {
		$('[fill]').removeAttr('fill');
		$('[style]').removeAttr('style');
	},
	parserOptions: { xmlMode: true }
}))

But I noticed one bug in this plugin - sometimes it converts the '>' character to the encoding '>'.
The following piece of task solves this problem:
.pipe(replace('>', '>'))

Now let's make a sprite from the resulting one and put it in the folder:
.pipe(svgSprite({
		mode: "symbols",
		preview: false,
		selector: "icon-%f",
		svg: {
			symbols: 'symbol_sprite.html'
		}
	}
))
.pipe(gulp.dest(assetsDir + 'i/'));

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

Pinch of styles


Now we need to make styles for our sprite (in this case, a .scss file). In the gulp-svg-sprites plugin, we can specify this file, but here's the annoyance - it cannot be done with this setting:
mode: "symbols"

I decided to make a separate task for creating scss. If you find another solution, write in the comments.
// 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/_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 include it).
In the templates property, we declare where to get the template for it. My 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 _svg_sprite.scss with the following content:
.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;
}

Compiled css would 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 through em , which will allow us to further manage them through font-size.
We compose the final task to run one command:
gulp.task('svgSprite', ['svgSpriteBuild', 'svgSpriteSass']);

Connect to the page


So we got an html file with icons and a scss file with decoration. Next, connect the file to the page using caching through localStorage. This process is described in detail in the Caching SVG Sprite in localStorage article .
We include the js file of the following content:
;( 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 ) );

That's all, we have connected our file to the page, after the first download it is cached.
I embed the icons through the jade mixin, as It is fast and convenient:
mixin icon(name,mod)
	- mod = mod || ''
	svg(class="icon icon-" + name + ' ' + mod)
		use(xlink:href="#icon-" + name)

Now, to embed the icon, call the mixin with its name:
+icon('check_round','red_mod')
+icon('burger','green_mod')

Resulting html:

Open the page in the browser:



While the size of the icons is full size and have a standard color. Change this (not in the generated file, but in the main one):
.icon-burger {
	font-size:3rem;
	&.green_mod {
		fill:green;
	}
}
.icon-check_round {
	font-size:3rem;
	&.red_mod {
		fill: red;
	}
}

Result:

That's all, we got a working system for connecting icons through sprites, but there is one more thing.

Blur


Unfortunately, not all designers make icons on a pixel grid. In this case, the icons will “blur”. If you export icons from illustrator you need to enable 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 to align them correctly.

Special thanks to @akella , who helped me write this solution.

Also popular now: