Привет, разработчик!

При верстке макета из PSD часто иконки вставлены в формате SVG, а если нет — просим их у дизайнера. Ранее мы использовали иконочные шрифты, но недавно увидели преимущества спрайтов и решили попробовать с ними поиграться внедрить их в процесс разработки. Нам нравятся иконочные шрифты, но они имеют ряд недостатков(на эту тему почитайте CSSTricks). Эксперимент удался, и вот как мы организовали систему.

Условия

Что нам нужно от спрайтов:

  1. Гибкое управление размером, цветом и поведением(hover, focus etc) иконки
  2. Автоматизация, минимум ручной работы
  3. Кеширование иконок для хорошей скорости загрузки страниц
  4. Удобная вставка иконок в разметку страницы (для шаблонизации html я использую jade)

Структура папок:

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

Подробнее о том как работает наша сборка - можете почитать в репозитории. Для создания спрайта используем gulp, а именно:

  • gulp-svg-sprites - создание спрайта
  • gulp-svgmin - минификация SVG
  • gulp-cheerio - удаление лишних атрибутов из svg
  • gulp-replace - фиксинг некоторых багов, об этом ниже

Поехали

Устанавливаем плагины(мы это делаем глобально и потом линкуем):

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

В gulpfile объявляем плагины:

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

Варим спрайт

Первый таск - создаем html-файл с тегами symbol.

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/'));
});

Давайте разберемся, что тут происходит по частям.

Говорим откуда нам нужно взять иконки и минифицируем их. Переменная assetsDir - для удобства.

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

Удаляем атрибуты style и fill из иконок, для того чтобы они не перебивали стили, заданные через css.

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

Однако у данного плагина один баг - иногда он преобразовывает символ ‘>’ в кодировку '>'.
Эту проблему решает следующий кусок таска:

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

Теперь сделаем из получившегося спрайт и положим в папку:

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

symbol_sprite.html - и есть наш спрайт. Внутри он будет содержать следующее(для простоты приведена пара иконок):

<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>

Щепотка стилей

Теперь нам нужно сделать стили для нашего спрайта(в данном случае файл .scss). В плагине gulp-svg-sprites мы можем задать этот файл, но вот какая досада - его нельзя сделать при данной настройке:

mode: "symbols"

Сделаем для создания 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/'));
});

В свойстве cssFile объявляем, куда положить на scss файл(потом инклудим его).
В свойстве templates объявляем, где взять для него шаблон. Код шаблона:

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

Получаем _svg_sprite.scss следующего содержания:

.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;
}

Скомпилированный css будет таким:

.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;
}

Обратите внимание, что размеры иконок выражены через em, что позволит нам в дальнейшем управлять ими через font-size.
Составляем итоговый таск, и запускаем его:

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

Подключаем на страницу

Итак мы получили html-файл с иконками и scss-файл с оформлением. Далее подключим файл на страницу, используя кеширование через localStorage. Этот процесс подробно описан в статье Caching SVG Sprite in localStorage.
Подключаем js-файл следующего содержания:

;( 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 ) );

Файл подключен, после первой загрузки он кешируется. Если вам нужно нужно обновить спрайт в js-файле приведенном выше меняйте параметр revision на +1. Иконки вставляем через миксин jade, т.к. это быстро и удобно:

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

Теперь, чтобы встроить иконку вызываем миксин с её именем:

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

Результирующий 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>

Открываем страницу в браузере:

Пока размеры иконок в натуральную величину и имеют стандартный цвет. Изменим это(не в сгенерированном файле, а в главном):

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

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

Результат:

Вот и все, мы получили рабочую систему подключения иконок через спрайты, но есть еще один момент.

Размытие

К сожалению, не все дизайнеры делают иконки по пиксельной сетке. В этом случае иконки будут “размываться”. Если вы экспортируете иконки из иллюстратора вам нужно включить пиксельную сетку и подогнать размер и расположение иконки под пиксельную сетку. Если вы работаете в готовыми svg-файлами - воспользуйтесь сервисом iconmoon для их правильного выравнивания.

На этом все.