7 июля 2014 в 09:13

Генерация CSS-спрайтов с Gulp tutorial


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

Вариантов было несколько:

Первый вариант сложен в установке, есть несколько зависимостей, для которых приходилось ставить дополнительные пакетные менеджеры. Если в проект добавится новый разработчик — придется обьяснять ему что к чему. А это не тот путь, который мы выбрали. Также, не было гибкой настройки расположения картинок в спрайте.

Остальные 3 варианта — это реализации на одном движке spritesmith. В итоге выбор пал на официальный порт для Gulp.

Установка


Самое первое, что надо сделать — установить Gulp на компьютер. Официальная документация поможет вам справиться с этим шагом.
Затем ставим gulp.spritesmith. В моем случае проект чистый, поэтому я ставлю все необходимые зависимости.

npm i gulp gulp-stylus gulp.spritesmith --save


Теперь можно переходить к настройке генератора.

Настройка


Перед тем как приступить непосредственно к описанию таска, ознакомимся с параметрами, которые принимает функция.

  • imgName — имя генерируемой картинки
    • поддерживаются расширения .png и .jpg/.jpeg (зависит от используемого движка)
    • формат картинки, может быть переписан свойством imgOpts.format
  • cssName — имя css файла, который получится на выходе
    • поддерживаемые CSS расширения .css (CSS), .sass (SASS), .scss (SCSS), .less (LESS), .styl/.stylus (Stylus) и .json (JSON)
    • расширение может быть переписано свойством cssFormat
  • imgPath — путь к спрайту, будет записываться в CSS
  • engine — движок, используемый для генерации спрайта
    • по умолчанию стоит auto и будет использован наиболее подходящий движок на вашей системе
    • поддерживаемые значения phantomjs, canvas, gm, pngsmith
  • algorithm — способ сортировки изображений
    • поддерживаемые значения top-down (по умолчанию), left-right, diagonal, alt-diagonal, binary-tree
  • padding — отступ между картинками. По умолчанию отступа нет
  • imgOpts — настройки спрайта
    • format — формат картинки
      • поддерживаются форматы png и jpg (зависит от используемого движка)
    • quality — качество, поддерживается только gm движком
    • timeout — задержка до завершения рендеринга в миллисекундах (поддерживается только phantomjs движком)
  • algorithmOpts — опции алгоритма
    • sort — включение/выключение сортировки изображений. По умолчанию стоит true
  • engineOpts — опции движка
    • imagemagick — true/false, приоритетное использование imagemagick вместо graphicsmagick (есть только в gm)
  • cssFormat — выбор формата CSS файла
    • поддерживаемые значения css (CSS), sass (SASS), scss (SCSS), scss_maps (SCSS используя map notation), less (LESS), stylus (Stylus) и json (JSON)
  • cssVarMap — цикл, позволяющий настраивать названия CSS переменных
  • cssTemplate — функция или путь до mustache шаблона, дающие возможность настроить CSS-файл на выходе
  • cssOpts — опции CSS-шаблонов
    • functions — пропустить генерацию миксинов
    • cssClass — цикл, переписывающий стандартные CSS-селекторы


Исходя из этого, самый простой таск будет иметь следующий вид:
gulp.task('sprite', function() {
    var spriteData = 
        gulp.src('./src/assets/images/sprite/*.*') // путь, откуда берем картинки для спрайта
            .pipe(spritesmith({
                imgName: 'sprite.png',
                cssName: 'sprite.css',
            }));

    spriteData.img.pipe(gulp.dest('./built/images/')); // путь, куда сохраняем картинку
    spriteData.css.pipe(gulp.dest('./built/styles/')); // путь, куда сохраняем стили
});


У нас получился такой спрайт:


И следующий CSS-код:
Скрытый текст
/*
Icon classes can be used entirely standalone. They are named after their original file names.

```html
<i class="icon-home"></i>
```
*/
.icon-home {
  background-image: url(sprite.png);
  background-position: 0px 0px;
  width: 16px;
  height: 16px;
}
.icon-home_hover {
  background-image: url(sprite.png);
  background-position: 0px -16px;
  width: 16px;
  height: 16px;
}
.icon-instagram {
  background-image: url(sprite.png);
  background-position: 0px -32px;
  width: 16px;
  height: 16px;
}
.icon-instagram_hover {
  background-image: url(sprite.png);
  background-position: 0px -48px;
  width: 16px;
  height: 16px;
}
.icon-pin {
  background-image: url(sprite.png);
  background-position: 0px -64px;
  width: 12px;
  height: 16px;
}
.icon-pin_hover {
  background-image: url(sprite.png);
  background-position: 0px -80px;
  width: 12px;
  height: 16px;
}
.icon-tras_hover {
  background-image: url(sprite.png);
  background-position: 0px -96px;
  width: 16px;
  height: 16px;
}
.icon-trash {
  background-image: url(sprite.png);
  background-position: 0px -112px;
  width: 16px;
  height: 16px;
}
.icon-user {
  background-image: url(sprite.png);
  background-position: 0px -128px;
  width: 16px;
  height: 16px;
}
.icon-user_hover {
  background-image: url(sprite.png);
  background-position: 0px -144px;
  width: 16px;
  height: 16px;
}




Тонкая настройка

У нас в проекте используется CSS-препроцессор Stylus, поэтому мне удобнее будет сохранять это как .styl файл с переменными.
Для компактности я включил алгоритм распределения картинок binary-tree. Всем переменным, для наглядности, я даю префикс s-. Отключаю генерацию миксинов и выношу их в отдельный файл. И создаю свой CSS-шаблон, потому, что по умолчанию генерируется много лишнего мусора, который порядочно раздувает файл и мной не используется.

В итоге, спрайт будет иметь следующий вид:


js + stylus код
gulp.task('sprite', function() {
    var spriteData = 
        gulp.src('./src/assets/images/sprite/*.*') // путь, откуда берем картинки для спрайта
            .pipe(spritesmith({
                imgName: 'sprite.png',
                cssName: 'sprite.styl',
                cssFormat: 'stylus',
                algorithm: 'binary-tree',
                cssTemplate: 'stylus.template.mustache',
                cssVarMap: function(sprite) {
                    sprite.name = 's-' + sprite.name
                }
            }));

    spriteData.img.pipe(gulp.dest('./built/images/')); // путь, куда сохраняем картинку
    spriteData.css.pipe(gulp.dest('./src/styles/')); // путь, куда сохраняем стили
});

$s-book = 16px 0px -16px 0px 16px 16px 80px 64px 'sprite.png';
$s-book_hover = 48px 16px -48px -16px 16px 16px 80px 64px 'sprite.png';
$s-comments = 0px 16px 0px -16px 16px 16px 80px 64px 'sprite.png';
$s-comments_hover = 16px 16px -16px -16px 16px 16px 80px 64px 'sprite.png';
$s-compose = 32px 0px -32px 0px 16px 16px 80px 64px 'sprite.png';
$s-compose_hover = 32px 16px -32px -16px 16px 16px 80px 64px 'sprite.png';
$s-faceboo_hover = 0px 32px 0px -32px 16px 16px 80px 64px 'sprite.png';
$s-facebook = 16px 32px -16px -32px 16px 16px 80px 64px 'sprite.png';
$s-globe = 32px 32px -32px -32px 16px 16px 80px 64px 'sprite.png';
$s-globe_hover = 48px 0px -48px 0px 16px 16px 80px 64px 'sprite.png';
$s-home = 0px 0px 0px 0px 16px 16px 80px 64px 'sprite.png';
$s-home_hover = 48px 32px -48px -32px 16px 16px 80px 64px 'sprite.png';
$s-instagram = 0px 48px 0px -48px 16px 16px 80px 64px 'sprite.png';
$s-instagram_hover = 16px 48px -16px -48px 16px 16px 80px 64px 'sprite.png';
$s-pin = 32px 48px -32px -48px 12px 16px 80px 64px 'sprite.png';
$s-pin_hover = 44px 48px -44px -48px 12px 16px 80px 64px 'sprite.png';
$s-tras_hover = 64px 0px -64px 0px 16px 16px 80px 64px 'sprite.png';
$s-trash = 64px 16px -64px -16px 16px 16px 80px 64px 'sprite.png';
$s-user = 64px 32px -64px -32px 16px 16px 80px 64px 'sprite.png';
$s-user_hover = 64px 48px -64px -48px 16px 16px 80px 64px 'sprite.png';



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


Спрайт сгенерирован, stylus файл с переменными есть — что дальше?
Дальше нам помогут со всем этим работать миксины, которые генерирует по умолчанию плагин и которые мы отключили.
Для них я создал отдельный файл mixins.styl.

Содержимое файла mixins.styl:
spriteWidth($sprite) {
  width: $sprite[4];
}

spriteHeight($sprite) {
  height: $sprite[5];
}

spritePosition($sprite) {
  background-position: $sprite[2] $sprite[3];
}

spriteImage($sprite) {
  background-image: url(../images/$sprite[8]);
}

sprite($sprite) {
  spriteImage($sprite)
  spritePosition($sprite)
  spriteWidth($sprite)
  spriteHeight($sprite)
}



Основной миксин для нас это sprite($sprite). Вместо $sprite вставляем нужную нам переменную. Например, sprite($s-home).
Результат получится такого вида:
background-image:url("../images/sprite.png");
background-position:0 0;
width:16px;
height:16px

Миксин позволяет нам сразу выводить ширину и высоту картинки — это очень удобно, особенно при использовании псевдоэлементов для оформления.
Рабочий пример

Проблемы


За все время использования этого решения я встретил только одну проблему.
При :hover и :active иконка будет мигать. Происходит это потому, что миксин sprite каждый раз генерирует background-image и при ховере браузер подставляет эту картинку.
Немного подумав и почитав документацию stylus, было найдено решение.
Нам просто нужно проверять наличие вышеперечисленных псевдоклассов у селектора. Если они есть, то мы пропускаем вывод spriteImage($sprite).

Финальный mixin
sprite($sprite)
    if !match('hover', selector()) && !match('active', selector())
        spriteImage($sprite)
    spritePosition($sprite)
    spriteWidth($sprite)
    spriteHeight($sprite)


К сожалению, все варианты предусмотреть не получится — иногда это может быть изменение класса через js, поэтому мы можем просто использовать
spritePosition($sprite)

если картинка была объявлена ранее.

Итог


С этим решением я работаю почти месяц и могу сказать, что оно экономит очень много времени. Стремитесь автоматизировать любую рутину и использовать свое время максимально эффективно.

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

Ссылки


Алексей Крекотун @Dobrii
карма
9,0
рейтинг 0,0
Самое читаемое Разработка

Комментарии (47)

  • +5
    Хм, а я постепенно ушёл от спрайтов в сторону base64.
    Для grunt / gulp также есть плагины с генерацией кода в синтаксисе препроцессоров. Чистота исходников поддерживается.

    Пользуясь случаем шлю лучи ненависти тем front-end'щикам, которые просто кидают base64 в файл.
    • 0
      Прям все что в спрайтах раньше размещалось теперь в base64? Интересует годный плагин под grunt с таким функционалом.
    • +1
      > лучи ненависти тем front-end'щикам, которые просто кидают base64 в файл

      Вы про отдельный css файл или файл, в котором весь другой css?
      • 0
        Я про то, когда в исходном коде просто лежит хеш и непонятно что это за картинка.
        Так просто её заменить, например, не быстро: надо сохранить её из браузера, отмодифить, получить заново хеш и скопировать в файл.

        У нормальных пацанов в исходнике должен быть путь до картинки, а сама она в репозитории. Преборазование в base64 должно происходить при компиляции ( grunt, rails etc. )
  • 0
    Да, забыл спросить, а вы оптимизатор какой-нибудь натравливаете на спрайт после?
    • 0
      При выгрузке на продакшн можно поставить, при каждом обновлении спрайта нет смысла.
  • +1
    Имеет смысл добавить возможность генерить несколько спрайтов из подпапок ./src/assets/images/sprite/ИМЯ_СПРАЙТА/*.* и заточить автоматизацию для ретиновых спрайтов чтобы из файлов с @2x собирался отдельный спрайт
    • 0
      Текущие б плагины заставить нормально работать… На днях ломалась сборка из-за ошибки в одном из плагинов (в зависимостях) github.com/gulpjs/gulp/issues/561.
      И на Ubuntu есть некоторые проблемы с imagemin (зависимости) и кэшированием изображений. У Grunt'а в свое время таких проблем не заметил.
    • 0
      Ну такие решения есть, правда не в grunt / gulp модулях
      1. https://github.com/jorgebastida/glue
      2. https://github.com/jakesgordon/sprite-factory
  • 0
    Сделать генерацию из подпапок несложно, моя цель была показать базовые возможности.
    • 0
      подозревал что это так, но все никак не мог это реализовать, не поделитесь примером?
      • 0
        Сейчас на проекте решение «в лоб». Под каждую папку создавалась отельная переменная со своими настройкаим.
        Автоматический подхват вложенных папок я встретил у www.npmjs.org/package/gulp-sprite-generator.
        У меня в планах подробнее рассмотреть эту обертку над spritesmith
  • 0
    Вы еще используете спрайты для одноцветных иконок?
    Тогда для вас одна из альтернатив, да, у этой есть небольшие баги (неправильно работает с translate в svg), но использовать можно.

    Pros:
    * Занимает меньше места
    * не нужна спец версия для ретин
    * это текст, поэтому на него распространяются все css свойства текста
    * отлично анимируется (т.к. текст же)

    Cons:
    * 3rd-party сервис, хотя есть сорсы, так что не факт что минус
    * вид будет немного меняться в зависимости от системы и её настроек
    • 0
      К сожалению, иконки в виде шрифта — решение не очень хорошее из-за разных типов сглаживания в браузерах и ОС.
      Так иконки в хроме выглядят хорошо, а в фф уже плохо. (Пример: github)

      Если можно закрыть на это глаза, то шрифты — хорошее решение. Также заслуживает внимания и svg
      • 0
        Разница просто ужасная!!!
        image
        кликабельно
        • +1


          Мне неприятно смотреть на размытые иконки, например.
          Дизайнеры тоже плохо относятся к этому.
          • –1
            А как дизайнеры относятся к размытым иконкам на ретине или hidpi, коих становится все больше? Или вы генерируете по 2 спрайта на каждую расцветку иконок?
            • 0
              Под ретину готовится отдельный спрайт, в чем проблема?
              • 0
                Ну я про это и говорю: 2 спрайта на каждую иконку каждого цвета.
                • 0
                  Почему нельзя иконки разных цветов собрать в одном спрайте для 1 разрешения?
                  Или я не понял, что вы имел ввиду под расцветкой иконок.
        • +1
          Разница весьма неприятная
  • 0
    gulp-sprite-generator был написан как раз из-за невозможности юзать официальный порт spritesmith в «реверс» режиме — из css в css + sprites, без миксинов и прочего лишнего кода =)
    • 0
      Я немного не понимаю, зачем нужен «реверс» режим?
      • 0
        Ну, изначально, у вас есть просто картинки для использования в css. Они совсем не спрайты, и даже разные состояния, скажем, кнопочек — у вас тоже разные картинки. Это исходники, точно такие же, как любой другой код. При сборке проекта, вы эти картинки, как и любой другой код, компилируете по какой-то логике. Когда вы, например, используете uglify, вы же не пишете для этого процесса какой-то код в исходниках? Точно так же и со спрайтами — вам не нужно нигде в .less или .css файлах использовать миксины, полученные, по факту, при компиляции исходников изображений =) Т.е. разработчик css и картинок совсем не должен знать о том, что произойдет с его творением при билде =)
        • 0
          А, я просто привык делать сразу спрайт и стараюсь не допускать таких моментов, когда я или кто-то делает на ховеры/эктивы и другие состояния разные картинки.
          Я должен видеть сайт таким, как он будет в итоге и тестировать его работу.

          Ваш плагин поддерживает .styl/.less и другие препроцессоры?
          Можно ли отключить реверс режим?
          Хочу пощупать его потому, что есть поддержка ретины.
          • 0
            Ну да, вопрос в идеологии разработки еще.

            Плагину по сути без разницы, какой файл ему скормить. Все, что он делает — это склеивает картинки в спрайты через spritesmith и меняет ссылки на них с правильным позиционированием.

            Поддержки ретины нет, есть только поддержка нотации (img@2x.png), на основании которой, можно правильно группировать спрайты =)
            • 0
              Насчет ретины.
              На выходе в css мы получим background-size/2 и позиции деленные на 2?
              • 0
                Да )
                • 0
                  Звучит хорошо :)
                  • 0
                    Если что — велкам в issues =)
                    • 0
                      На днях поиграюсь и отпишу, если что
  • 0
    при увеличении масштаба страницы по ctrl+ у некоторых иконок появляются вертикальные/горизонтальные полоски по бокам от соседних иконок (особенно в хроме)

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

    • 0
      Можно :)
  • 0
    делайте SVG спрайты и будет вам счастье!
    * меншье весит
    * ретина ready
    * font-icons почти то же, но глючат на мобильных (в частности windows phone) и вообще не айс
    * кодировать в base64 плохо для мобилок, долго декодируются, используйте acsii

    всё вышеописанное + grunt + дополнительные приятные плюшки (4 CSS на выходе для старых браузеров, JS-loader для них с определением поддержки SVG, генерация preview, сжатие png, etc) = Iconizr github.com/jkphl/grunt-iconizr
    • 0
      Раз такая пьянка, надо написать обертку для галпа :)
      • 0
        зачем?
        у автора есть в планах, но не понятно когда, уже спрашивал ))
        github.com/jkphl/grunt-iconizr/issues/9

        можно попробовать через «прокси», всё руки не доходят — github.com/gratimax/gulp-grunt
        • 0
          Уже начал писать свою обертку :)
          • 0
            Как успехи?
            • 0
              Как начал, так и приостановил работу, появились другие неотложные задачи.
              Постараюсь найти время на этой или следующей неделе, чтобы дописать.
            • 0
              автор уже переписал компонент «svg-sprite», на неделе-другой обновит «grunt-iconizr» и напишет «gulp-iconizr»
              github.com/jkphl/grunt-iconizr/issues/23#issuecomment-69060415
  • 0
    Огромное спасибо за решение!!!
    • 0
      Пожалуйста :)
      Я бы рекомендовал переходить на иконочные шрифты или свг
      • 0
        Да шрифтовые иконки это круто, но часто в общий концепт они не подходят вот и приходиться рисовать самим и делать спрайты.
  • 0
    Немного подумав и почитав документацию stylus, было найдено решение.


    Мое решение
    Каждой иконке для состояния :hover, дать такое имя icon-(имя икоки):hover
    Создать файл темплейт с таким содержимым ( в моем случае это sass, для less,scss допишите скобки )

    {{#block "spritesheet"}}
    .icon:before
      content: ''
      background-image: url('/static/png/{{{spritesheet.escaped_image}}}')
      background-size : {{spritesheet.px.width}}, {{spritesheet.px.height}}
      display: inline-block
    {{/block}}
    
    {{#each sprites}}
    .icon.icon-{{name}}:before
      background-position: {{px.offset_x}} {{px.offset_y}}
      width: {{px.width}}
      height: {{px.height}}
    {{/each}}
    
    • 0
      Вариант
      • 0
        Можешь добавить этот вариант в пост

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.