Создание SVG спрайтов с помощью Gulp и Sass

В этом посте пойдёт речь о процессах и используемых инструментах для создания SVG спрайтов в студии Liquid Light.

Создание и поддержка больших SVG спрайтов может быть сложным делом и времязатратным. Поэтому мы решили автоматизировать процесс. И вместо использования одного большого SVG спрайта и отслеживания координат каждой иконки индивидуально, мы бы хотели иметь возможность редактировать каждую иконку и создавать и размещать сгенерированные иконки автоматически.

На практике это значит, что нам нужно сложить все наши SVG иконки в папку и будет создан SVG спрайт (и PNG резерв для IE8) и оптимизирован автоматически вместе с Sass картой или именами и скоординирован. Используя Sass миксины, мы далее сможем включать наши спрайты используя маленькие куски кода:

button {
    &:before {
        @include sprite(search);
    }
}

Весь код можно найти на Github

Автоматизация процесса

Для интеграции SVG спрайтов в наш рабочий процесс, мы решили, что хотим чтобы исполнитель задач создавал спрайт - это значит, что мы могли бы отдельно создавать и обновлять индивидуальные иконки без редактирования и обновления целого изображения. Gulp - это выбранный нами исполнитель задач, который кроме всего прочего может запускать gulp-svg-sprites. Также мы хотели, чтобы CSS создавались автоматически - с размерами и позициями фона, вычисляемые при создании. Это даёт нам преимущество в возможности определять размеры иконок и обновлять CSS, чтобы они это отображали.

По-умолчанию, плагин gulp-svg-sprites генерирует свой собственный CSS, но typo3 имеет свои собственные классы, поэтому нам нужен способ создания размеров и положений как переменные и использовать их дальше в существующих селекторах. Для этого мы решили обратится к Sass.

При использовании Sass, иконки хранятся в массиве - или “карте” (подробнее про Sass карты). Используя некоторые миксины, мы можем вызывать любую иконку в спрайте и после компиляции, выводить размеры и позицию фона для каждой иконки.

Этот пост - это не вступление в Gulp или Sass (уже есть куча подобных вступлений в веб, можете почитать вот эту статью от Mark Goodyear, Sitepoint и Codefellows ), скорре этот пост описывает детально рабочий процесс для создания и использования SVG спрайтов. Он выполняется через плагины gulp, задачи gulp, которые мы установили и специфические миксины.

Плагины Gulp - package.json

Во-первых, нам нужны плагины для запуска задач по созданию спрайта. Наш файл package.json будет выглядеть вот так:

{
  "devDependencies": {
    "gulp": "^3.8.7",
    "gulp-load-plugins": "^0.4.0",
    "gulp-svg-sprites": "^1.0.0",
    "gulp-svg2png": "^0.3.0",
    "gulp-svgo": "^0.1.1",
    "gulp-util": "^2.2.14"
  }
}

Пробежимся по каждому из используемых плагинов

  • gulp-load-plugins - см. ниже
  • gulp-svg-sprites - это тяжёлая артилерия, созадёт спрайты SVG и CSS
  • gulp-svg2png - преобразовывает SVG в PNG - будем использовать для создания нашего PNG резерва
  • gulp-svgo - SVG оптимизатор - для оптимизации наших SVG иконок перед тем, как мы создадим спрайт
  • gulp-util - используется для вывода цветных сообщений на экран

Плагин gulp-load-plugins (от Jack Franklin), который находится в package.json и загружает все плагины gulp как один большой проект - это значит, что вам не нужно указывать каждый из них в вашем gulp файле. Сэкономит чуть вам времени!

var gulp = require('gulp');

var gutil = require('gulp-util');
var plugins = require("gulp-load-plugins")({
    pattern: ['gulp-*', 'gulp.*'],
    replaceString: /\bgulp[\-.]/
});

После определения плагинов для их использования вам нужно вызывать нужный плагин в объекте.

.pipe(plugins.svg2png())

Gulp задача - gulpfile.js

У нас есть несколько gulp задач, чтобы сделать спрайт и соответствующие файлы (см. ниже). Первая задача svgSprite будет наблюдать за некоторой папкой; при добавлении или редактировании SVG файлов будет запускаться задача и будут созданы спрайт (с соответствующим scss файлом).

Отдельные SVG файлы проходят через оптимизатор SVG преед помещением в спрайт, чтобы обеспечить наименее возможный размер.

Далее, задача pngSprite конвертирует SVG в PNG спрайт для IE8 и ниже.

Мы храним все наши пути и плагины в объектах в начале файла, но они могут быть просто заменены в нужных местах ниже (полный файл для загрузки можно найти в Github репо).

Внимание Mac safari выдаёт разные результаты (при использовании em для положений фона), в отличие от других браузеров когда спрайт создан в вертикальном или горизонтальном виде. Диагональ - это единственный макет, когда все браузеры ведут себя одинаково.

Внимание Убедитесь, что ваш спрайт не превышает размеры 2300px x 2300px - иначе <= iOS7 вообще не покажет изображение.

 gulp.task('svgSprite', function () {

    return gulp.src(paths.sprite.src)
        .pipe(plugins.svgo())
        .pipe(plugins.svgSprites({
            cssFile: paths.sprite.css,
            preview: false,
            layout: 'diagonal',
            padding: 5,
            svg: {
                sprite: paths.sprite.svg
            },
            templates: {
                css: require("fs").readFileSync(paths.templates.src + 'sprite-template.scss', "utf-8")
            }
        }))
        .pipe(gulp.dest(basePaths.dest));

});

gulp.task('pngSprite', ['svgSprite'], function() {
    return gulp.src(basePaths.dest + paths.sprite.svg)
        .pipe(plugins.svg2png())
        .pipe(gulp.dest(paths.images.dest));
});

gulp.task('sprite', ['pngSprite']);

Scss шаблон - sprite-template.scss

Чтобы убедится, что данные выходят из задания svgSprites так, как нам нужно, мы передаётм в шаблон используя плейсхолденры для генерированния данных. Наша карта спрайтов содержит данные о спрайте как о целом, плюс отдельные иконки, находящиеся всередине спрайта сами по себе.

Наш sprite-template.scss выглядит вот так (добавил пару строк для читаемости):

{#common}
$icons: (
    sprite: (width: {swidth}px, height: {sheight}px, pngPath: '{pngPath}', svgPath: '{svgPath}'),
{/common}
{#svg}
    {name}: (width: {width}px, height: {height}px, backgroundX: {positionX}px, backgroundY: {positionY}px),
{/svg}
{#common}
    );
{/common}

Блоки {common} сообщают генератору шаблонов, выводить эти блоки только раз. Первый объект открывает карту и вставляет данные о спрайте (ширину, высоту, путь к PNG и путь к SVG). Блок {common} в конце просто закрывает карту Sass.

Блок {svg} в середине проходит циклом через каждый файл и назначает имя файла, разрешение и положение фона.

Используя этот шаблон вместе с плагином gulp-svg-sprite, получим следующее:

$icons: (
    sprite: (width: 104px, height: 96px, pngPath: '../img/sprite.png', svgPath: '../img/sprite.svg'),

    facebook: (width: 10px, height: 22px, backgroundX: 0px, backgroundY: 0px),
    twitter: (width: 32px, height: 22px, backgroundX: -20px, backgroundY: -32px),
    twitterHover: (width: 32px, height: 22px, backgroundX: -62px, backgroundY: -64px),
);

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

Scss миксины и функции

С сгенерированной картой, мы могли бы начать использовать значения нашего Scss файла следующим образом:

 @import "src/sprite";

.class {
    $twitter: map-get($icons, twitter);
    $sprite: map-get($icons, sprite);
    width: map-get($twitter, width);
    height: map-get($twitter, height);
    background-image: url(map-get($sprite, svgPath));
    background-position: map-get($twitter, backgroundX) map-get($twitter, backgroundX);
}

Вы поняли идею - это всё очень долго и нужно делать для каждой иконки, которую мы хотим использовать. Код выше не должен включать конвертирование px в em!

Чтобы избежать проблем, мы создали банк миксинов Scss, чтобы упростить использование спрайтов:

@import 'src/sprite';

$ieSprite: '.lt-ie9' !default;
$sprite: map-get($icons, sprite) !default;
$baseFontSize: 16px !default;

@import 'mixins';

.class {
    @include sprite(phone);
}

И вместо одного универсального миксина, мы разбили его на несколько простых миксинов, плейсхолдеров и функций - что значит, что мы можем вызывать отдельные атрибуты (например: нет смысла переназначать фоновое изображение или размеры иконки, если иконка изменяется при наведении на неё (:hover)).

Так как наши сайты поддерживают IE8, поэтому PNG и px резервы очень важны.

Переменные

Нужно установить парочку переменных, чтобы потом было легче поддерживать:

$ieSprite: '.lt-ie9' !default;
$sprite: map-get($icons, sprite) !default;
$baseFontSize: 16px !default;
  • $ieSprite - объявляет класс необходимый для ie спрайта. При отсутствии не будет сгенерирован IE PNG резерв.
  • $sprite - устанавливает переменную для основных данных спрайта (путь файла, размеры и др.)
  • $baseFontSize - используется для вычислений mq-px2em

Функции

Далее, извлекаются функции и возвращают указанный атрибут для указанной иконки из sass карты. Мы также включили библиотеку от Guardian sass-mq library, что значит у нас есть функция mq-px2em.

// Получение атрибутов из sass карты
@function sprite-attr($icon, $attr) {
    $icon: map-get($icons, $icon);
    @return map-get($icon, $attr);
}

@function icon-attr($icon) {
    $attr: (
        width: sprite-attr($icon, width),
        height: sprite-attr($icon, height),
        x: sprite-attr($icon, backgroundX),
        y: sprite-attr($icon, backgroundY)
    );

    @return $attr;
}

Плейсхолдеры

Плейсхолдеры устанавливают фон как спрайт SVG или PNG спрайт, если класс для body содержит .lt-ie9

// Установка фона и размера с IE резервом
%sprite {
    display: inline-block;
    background-image: url(map-get($sprite, svgPath));
    background-size: mq-px2em(map-get($sprite, width)) mq-px2em(map-get($sprite, height));
}
%ie-sprite {
     background-image: url(map-get($sprite, pngPath));
}

Миксины

Наконец у нас есть спрайт миксины. Есть миксин ie-sprite(), у которого схожый функционал, но он использует px вместо em и добавляет к селектору класс .lt-ie9.

// Для использования с gulp спрайт плагином
@mixin sprite($icon, $type: all) {
    @if $type == all {
        // Shares the backgrounds
        @extend %sprite;
    }

    $iconMap: icon-attr($icon);

    // Вывод размеров в em
    @if $type == all or $type == size {
        width: mq-px2em(map-get($iconMap, width) + 1);
        height: mq-px2em(map-get($iconMap, height) + 1);
    }

    // Вывод положения фона в em
    @if $type == all or $type == bg {
        background-position: mq-px2em(map-get($iconMap, x)) mq-px2em(map-get($iconMap, y));
    }

    // Добавление ie резерва
    @include ie-sprite($icon, $type);

}

Эти спрайт миксины берут 2 параметры - первый - это имя нужной иконки, второй - опциональный и позволяет пользователю определить размер size или фон bg для получения соответствующих атрибутов.

Использование

С установленным gulp процессом и при использовании миксинов, получить иконку для вывода очень просто:

class {
    &:before {
        @include sprite(twitter);
        content: '';
        float: left;
        margin-right: 0.5em;
    }

    &:hover {
        &:before {
            @include sprite(twitterHover, bg);
        }
    }
}

Вывод CSS:

.class:before {
    display: inline-block;
    background-image: url("../img/sprite.svg");
    background-size: 6.5em 6em;
}

.lt-ie9 .class:before {
    background-image: url("../img/sprite.png");
}

.class:before {
    width: 2.0625em;
    height: 1.4375em;
    background-position: -1.25em -2em;
    content: '';
    float: left;
    margin-right: 0.5em;
}
.lt-ie9 .class:before {
    width: 32px;
    height: 22px;
    background-position: -20px -32px;
}
.class:hover:before {
    background-position: -3.875em -4em;
}
.lt-ie9 .class:hover:before {
    background-position: -62px -64px;
}

Хотя кажется, что много кода, на самом деле не более, чем бы у вас заняло написание всего CSS вручную. SVG устанавливается как фон для любой иконки использующей спрайт (только если у них нет lt-ie9 класса для body, в этом случае они получают PNG). С этого момента, положение фона, ширина и высота устанавливаются для каждого селектора отдельно - с px резервом для IE8 и ниже.

Весь приведенный код может быть найден на Github.

Если у вас есть какие-то замечания или предложения по улучшению кода, то комментируйте ниже или делайте это на гитхабе через пулл-реквест!

Источник - статья Creating SVG Sprites using Gulp and Sass