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

При верстке макета из 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-sprite - создание спрайта
  • gulp-svgmin - минификация SVG
  • gulp-cheerio - удаление лишних атрибутов из svg
  • gulp-replace - фиксинг некоторых багов, об этом ниже

Поехали

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

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

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

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

Готовим спрайт

Создаем svg-файл с тегами symbol.

gulp.task('svgSpriteBuild', function () {
	return gulp.src(assetsDir + 'i/icons/*.svg')
	// minify svg
		.pipe(svgmin({
			js2svg: {
				pretty: true
			}
		}))
		// remove all fill, style and stroke declarations in out shapes
		.pipe(cheerio({
			run: function ($) {
				$('[fill]').removeAttr('fill');
				$('[stroke]').removeAttr('stroke');
				$('[style]').removeAttr('style');
			},
			parserOptions: {xmlMode: true}
		}))
		// cheerio plugin create unnecessary string '>', so replace it.
		.pipe(replace('>', '>'))
		// build svg sprite
		.pipe(svgSprite({
			mode: {
				symbol: {
					sprite: "../sprite.svg",
					render: {
						scss: {
							dest:'../../../sass/_sprite.scss',
							template: assetsDir + "sass/templates/_sprite_template.scss"
						}
					}
				}
			}
		}))
		.pipe(gulp.dest(assetsDir + 'i/sprite/'));
});

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

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

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

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

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

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

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

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

.pipe(svgSprite({
	mode: {
		symbol: {
			sprite: "../sprite.svg",
			render: {
				scss: {
					dest:'../../../sass/_sprite.scss',
					template: assetsDir + "sass/templates/_sprite_template.scss"
				}
			}
		}
	}
}))
.pipe(gulp.dest(assetsDir + 'i/sprite/'));

dest:’../../../sass/_sprite.scss’ - здесь мы объявили, куда нужно генерировать стили для спрайта.

template: assetsDir + “sass/templates/_sprite_template.scss” - код шаблона, на основе которого будут генерироваться стили для спрайта.

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

<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<symbol viewBox="0 0 32 32" id="check">
    <path d="M26.664 6.27a.829.829 0 0 0-1.177 0L13.088 18.611a.826.826 0 0 1-1.178 0l-5.433-5.532a.825.825 0 0 0-1.177 0l-2.401 2.158a.83.83 0 0 0-.246.583c0 .215.087.44.247.603l5.478 5.749c.324.328.855.861 1.178 1.186l2.355 2.374a.834.834 0 0 0 1.178 0L29.019 9.83a.846.846 0 0 0 0-1.188l-2.356-2.373z"/>
</symbol>
<symbol viewBox="0 0 94 13" id="rating">
    <path d="M7 10.5l-4.11 2.16.78-4.58L.34 4.84l4.6-.67L7 0l2.06 4.17 4.6.67-3.33 3.24.78 4.58L7 10.5zm20 0l-4.11 2.16.78-4.58-3.33-3.24 4.6-.67L27 0l2.06 4.17 4.6.67-3.33 3.24.78 4.58L27 10.5zm20 0l-4.11 2.16.78-4.58-3.33-3.24 4.6-.67L47 0l2.061 4.17 4.6.67-3.33 3.24.779 4.58L47 10.5zm20 0l-4.109 2.16.779-4.58-3.33-3.24 4.6-.67L67 0l2.061 4.17 4.6.67-3.33 3.24.779 4.58L67 10.5zm24.771 3.073L87 11.064l-4.771 2.509.904-5.318-3.868-3.764 5.343-.778L87-1.128l2.393 4.841 5.343.778-3.868 3.764.903 5.318zM87 9.936l3.447 1.812-.654-3.842 2.792-2.717-3.856-.562L87 1.128l-1.729 3.499-3.856.562 2.792 2.717-.654 3.842L87 9.936z"/>
    <path d="M87 10.5l-4.109 2.16.779-4.58-3.33-3.24 4.6-.67L87 0v10.5z"/>
</symbol>
</svg>

Код шаблона для генерирования стилей представлен на гитхабе.

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

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

.icon-arr_round {
	font-size:(59/10)*1rem;
	width:(63/59)*1em;
}
.icon-check {
	font-size:(32/10)*1rem;
	width:(32/32)*1em;
}
.icon-rating {
	font-size:(13/10)*1rem;
	width:(94/13)*1em;
}
.icon-save {
	font-size:(57/10)*1rem;
	width:(51/57)*1em;
}

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

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

.icon-arr_round {
  font-size: 5.9rem;
  width: 1.0678em;
}

.icon-check {
  font-size: 3.2rem;
  width: 1em;
}

.icon-rating {
  font-size: 1.3rem;
  width: 7.23077em;
}

.icon-save {
  font-size: 5.7rem;
  width: 0.89474em;
}

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

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

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

Итак мы получили svg-файл с иконками и scss-файл с оформлением. Иконки вставляем через миксин jade, т.к. это быстро и удобно:

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

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

+icon('arr_round','red_mod')

Результирующий html:

 <svg class="icon icon-arr_round red_mod">
    <use xlink:href="i/sprite/sprite.svg#arr_round"></use>
</svg>

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

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

.icon-arr_round {
	font-size: 10rem;
	&.red_mod {
		color:red;
	}
}

Результат:

Кроссбраузерность

Мы подключаем иконку ссылаясь на внешний svg-файл, к сожалению не все браузеры поддерживают такой запрос. Для решения этой проблемы есть библиотека svg4everybody. Подключаем её и инициализируем:

$(document).ready(function () {
	svg4everybody({});
});

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

Некорректные иконки

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

На этом все.