Как мы используем SVG-спрайты
Привет, разработчик!
При верстке макета из PSD часто иконки вставлены в формате SVG, а если нет — просим их у дизайнера. Ранее мы использовали иконочные шрифты, но недавно увидели преимущества спрайтов и решили попробовать с ними поиграться внедрить их в процесс разработки. Нам нравятся иконочные шрифты, но они имеют ряд недостатков(на эту тему почитайте CSSTricks). Эксперимент удался, и вот как мы организовали систему.
Условия
Что нам нужно от спрайтов:
- Гибкое управление размером, цветом и поведением(hover, focus etc) иконки
- Автоматизация, минимум ручной работы
- Кеширование иконок для хорошей скорости загрузки страниц
- Удобная вставка иконок в разметку страницы (для шаблонизации 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 для их правильного выравнивания.
На этом все.