При верстке макета из PSD часто иконки вставлены в формате SVG. А если нет — прошу их у дизайнера. Ранее я использовал иконочные шрифты, но недавно увидел преимущества спрайтов и решил попробовать
Условия
Что я хочу получить от спрайтов:
- Гибкое управление размером, цветом и поведением(hover, focus etc) иконки
- Автоматизация, минимум ручной работы
- Кеширование иконок для хорошей скорости загрузки страниц
- Удобная вставка иконок в разметку страницы (для шаблонизации html я использую jade)
Моя структура папок:
+-- gulpfile.js # gulpfile
L--assets # здесь редактируем файлы
L-- jade/ # шаблонизатор html
L-- sass/ # стили
L-- js/ # скрипты
L-- i/ # картинки, сюда мы и будем вставлять спрайт
L--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/_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 ) );
Все, мы подключили наш файл на страницу, после первой загрузки он кешируется.
Иконки я встраиваю через миксин 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 для их правильного выравнивания.
Особую благодарность выражаю @akella, который помог мне в написании данного решения.
Комментарии (24)
Houston
07.12.2015 11:24Для вебпака тоже такое есть, если кому нужно github.com/kisenka/svg-sprite-loader
TNK
07.12.2015 15:20Есть же gulp-svgstore. Вместо кэширования в localStorage предпочитаю использовать внешний файл-спрайт и svg4everybody. Ещё покорёбило, что результат склейки svg назвали .html.
gatilin222
07.12.2015 15:48Так я и показал, что это один из способов решения) Используйте те плагины, которые вам удобны)
Iskin
07.12.2015 18:22+1Я инлайню SVG прямо в CSS. С помощью postcss-svg можно сменить цвет. Правда нет анимаций, зато опрятнее выходит — меньше магии и нормальное кеширование.
Large
08.12.2015 17:33при подключении через символ тоже не работает цсс анимация.
gatilin222
08.12.2015 18:23Друг, вы не правы, все замечательно работает)
Large
08.12.2015 18:56jsfiddle.net/v5y027r1 варианты с use так же не работают. если у вас есть рабочий вариант — поделитесь.
Обсуждали здесь — habrahabr.ru/company/devexpress/blog/269331Large
08.12.2015 19:24налажал в коде выше. вот правильный вариант jsfiddle.net/v5y027r1/5 проверять в хроме.
gatilin222
08.12.2015 20:04Я имел ввиду transition) А то что animation не работает — не страшно, он и не нужен для иконок(для меня)
Iskin
07.12.2015 18:23Вообще, конечно, кеширование в localStorage — очень неправильный подход. localStorage создан для данных, а не кеша ресурсов. Например, когда пользователь нажмёт «Очистить кеш», то localStorage не будет очистен.
JiLiZART
08.12.2015 02:05Кешем могут теперь заниматься модные нынче service worker'ы dev.opera.com/service-worker.js
jmaks13
08.12.2015 09:52Если кому то интересно, то можно создать svg спрайт иначе, вот статья
www.liquidlight.co.uk/blog/article/creating-svg-sprites-using-gulp-and-sass
Punk_UnDeaD
09.12.2015 14:32> Результирующий html:
Традиционно ужасен. Это не упрёк автору, просто констатация факта.
Xu4
14.12.2015 04:14+1Но я заметил у данного плагина один баг — иногда он преобразовывает символ '>' в кодировку '&g t;', только без пробела между символами 'g' и 't'(если убрать пробел — получится символ).
Это замечание относится конкретно к статье на Хабре: вы не учли, что в кодах есть символ «&», который можно написать как «&». Соответственно, если вы хотите написать «>», то нужно писать это так: &gt; — оно при публикации статьи превратится в >
Надеюсь, это поможет не только вам, но и любому человеку, который не знает, как выводить в статьях HTML-коды в ситуации, когда они конвертируются в нормальные символы при вводе.
Grawl
О как хитро – через localStorage. Я догадался только в data:URI кодировать и подключать CSS-файл с картинками (пусть и SVG) как отдельный ресурс, но в таком случае их перекрашивать не выйдет. А через inline SVG это куда опрятнее выходит. Здорово всё выглядит.
Отдельная благодарность за Sass и Jade. Божественная связка.
gatilin222
Про localStorage подсказали на @cssunderhood)
Eklykti
А если на клиенте вдруг выключен js, то вместо иконки юзер увидит хрен?
gatilin222
osvaldas.info/caching-svg-sprite-in-localstorage Здесь написано, что делать в этом случае.
dom1n1k
Я как ни старался, никакой божественности в Jade не ощутил. Есть, конечно, свои плюсы и сильные стороны, но некоторое количество минусов перечеркивают их полностью. Проще по старинке сверстать.
gatilin222
Согласен, нет ничего божественного, просто подходит под мои задачи)