Не так давно компания, в которой я работаю, наконец-то окончательно отказалась от поддержки IE8 и, как следствие, я вплотную занялся вопросом перехода с растровых иконок на векторные. Основные плюсы SVG — это масштабирование, малый вес и возможность стилизации через CSS.

Сначала я попробовал воспользоваться техникой спрайтов, просто использовав вместо растра вектор, но тут возникло две проблемы:
  • Масштабирование. При произвольных размерах элементов спрайта для того, чтобы его точно отпозиционировать, приходится танцевать с бубном. И при изменении размера самого спрайта (например, при добавлении нового элемента) все может поползти.
  • Возможность стилизации посредством CSS отсутствует, потому что физически SVG-картинки в HTML-коде нет.


Тут я вспомнил про интеграцию картинок в CSS посредством кодирования оных base64 и использования data-uri. А с SVG все гораздо проще.Поскольку SVG-изображение — это XML-файл, то для него даже base64 не требуется, достаточно указать mime-тип как data:image/svg+xml и вставить содержимое SVG-файла одной строкой.

Возьмем, к примеру, иконку корзины:

image

Ее код выглядит примерно так:

<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" version="1.1" viewBox="0 0 1850 1635" xmlns:xlink="http://www.w3.org/1999/xlink">
 <g id="icon__cart">
  <path fill="#101010" d="M1254 1163l-1203 0c-26,0 -51,-25 -51,-51l0 -131c0,-23 22,-51 51,-51l1075 0c22,0 36,-13 44,-30l96 -192c4,-9 1,-12 -8,-12l-1207 0c-22,0 -51,-25 -51,-51l0 -131c0,-23 23,-51 51,-51l1298 0c27,0 46,-13 56,-33l200 -401c9,-18 20,-29 41,-29l182 0c19,0 29,7 18,29l-554 1111c-5,10 -19,23 -38,23z"/>
  <circle fill="#101010" cx="207" cy="1453" r="181"/>
  <circle fill="#101010" cx="1062" cy="1453" r="181"/>
 </g>
</svg>

Соответственно, использование ее в качестве фона в data-uri будет выглядеть так:

.element_whis_svg_background {
  background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" version="1.1" viewBox="0 0 1850 1635" xmlns:xlink="http://www.w3.org/1999/xlink">...</svg>');
}

Пробуем. В Хроме и Опере все замечательно, а вот в IE9-10-11, Safari, и, как ни странно, Firefox — фоновая картинка не отображается, они не понимают такой формат записи. Лезем искать, в чем проблема и выясняем, что для IE и прочих для содержимого SVG-файла надо выполнить URI-кодирование строки по стандарту RFC 3986. Кодируем, получаем вот такую конструкцию:

%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201850%201635%22%3E%3Cg%20fill%3D%22%23101010%22%3E%3Cpath%20d%3D%22M1254%201163H51c-26%200-51-25-51-51V981c0-23%2022-51%2051-51h1075c22%200%2036-13%2044-30l96-192c4-9%201-12-8-12H51c-22%200-51-25-51-51V514c0-23%2023-51%2051-51h1298c27%200%2046-13%2056-33l200-401c9-18%2020-29%2041-29h182c19%200%2029%207%2018%2029l-554%201111c-5%2010-19%2023-38%2023z%22%2F%3E%3Ccircle%20cx%3D%22207%22%20cy%3D%221453%22%20r%3D%22181%22%2F%3E%3Ccircle%20cx%3D%221062%22%20cy%3D%221453%22%20r%3D%22181%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E

Подставляем ее в background, проверяем:

.element_whis_svg_background {
  background: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2 ... svg%3E');
} 

Ура, все работает! Нюанс — кодировку в данном случае указывать не нужно. Если указать — то перестанет работать в IE.

Ну и, наконец, для того, чтобы внешний вид иконки можно было настраивать, используем любой препроцессор (я использую LESS, но это не принципиально) и оформим наш класс иконки как примесь (mixin). Для того, чтобы можно было менять цвет заливки, находим в кодированной строке атрибут fill, там будет что-то типа:

 fill%3D%22%23101010%22

То, что находится между %23 и %22 — и есть искомый код цвета, на место которого надо подставить переменную. В итоге получим следующее:

.icon__cart(@width, @height, @fill) {
  content: '';
  display: block;
  width: @width;
  height: @height;
  background-size: contain;
  background-repeat: no-repeat;
  background: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201850%201635%22%3E%3Cg%20fill%3D%22%23@{fill}%22%3E%3Cpath%20d%3D%22M1254%201163H51c-26%200-51-25-51-51V981c0-23%2022-51%2051-51h1075c22%200%2036-13%2044-30l96-192c4-9%201-12-8-12H51c-22%200-51-25-51-51V514c0-23%2023-51%2051-51h1298c27%200%2046-13%2056-33l200-401c9-18%2020-29%2041-29h182c19%200%2029%207%2018%2029l-554%201111c-5%2010-19%2023-38%2023z%22%2F%3E%3Ccircle%20cx%3D%22207%22%20cy%3D%221453%22%20r%3D%22181%22%2F%3E%3Ccircle%20cx%3D%221062%22%20cy%3D%221453%22%20r%3D%22181%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E');
} 

Используется данная примесь следующим образом (образец произвольный):

.block-whis-icon._cart {
  ...;

  &::before {
    .icon__cart(30px, 20px, '1d9fbb');
    ...;
  } 
} 

Вот, собственно, и все. LESS- (SASS, Stylus, etc.) файлик с такими примесями отлично заменяет спрайт.

Для оптимизации и кодирования SVG я использовал GULP, плагины gulp-svgo и gulp-data-uri-stream. Да, этот метод дает возможность менять лишь размеры иконки, цвет заливки и обводки, но в большинстве своем этого вполне достаточно.

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


  1. bubuger
    12.05.2015 18:08
    -2

    А не проще в Base64 svg загнать, раз уж конвертите файл? Да и занимать он будет как правило меньше в байтах.


    1. Fayon Автор
      12.05.2015 18:18
      +1

      Нет. Вся эта свистопляска задумывалась для возможности менять цвет иконки по необходимости. В base64 переменную цвета не подставишь.


      1. bubuger
        12.05.2015 18:23
        -2

        а что мешает в svg указать цвет через переменную currentColor fill=«currentColor» и менять дальше по мере вызова svg в css через атрибут color?


        1. Fayon Автор
          12.05.2015 18:30

          Это не работает в случае, когда svg используется как фоновая картинка.


    1. mkuzmin
      12.05.2015 18:43
      +2

      Можно еще post-css использовать. Написать плагин, который делает URI-кодирование для svg строк в background.


      1. Fayon Автор
        12.05.2015 22:27

        Увы, с post-css я не работал. Впрочем, можно и для gulp плагин написать, но, увы, я не настолько хорошо знаю ноду, чтобы плагин не только делал URI-кодирование, но и вставлял переменные в нужные места((


        1. mkuzmin
          13.05.2015 11:30

          вот нашел плагин для post-css, может поможет github.com/borodean/postcss-assets#inlining-files


  1. bolk
    12.05.2015 19:47
    +4

    Что-то я не понял о чём статья. Ну да, есть SVG и да, её можно вставить в data URI. Всё?

    Почитайте лучше вот этот блог: css.yoksel.ru или этот: pepelsbey.net там тоже про SVG, но глубоко и интересно.


    1. Fayon Автор
      12.05.2015 22:18

      Я столкнулся с конкретной проблемой — манипуляции цветом иконок, заданых через background-image. Для того, чтобы ее решить, мне пришлось активно покопаться в гугле. Вот для тех, кто столкнется с подобной проблемой или просто только начинает осваивать svg я и написал свой вариант решения. Если у вас есть лучше — я с удовольствием вас выслушаю.


      1. bolk
        12.05.2015 22:21

        Да пожалуйста: не ставить иконку фоном, а сделать обычной картинкой, цветом манипулировать через CSS.


        1. Fayon Автор
          12.05.2015 22:32

          А если этот вариант мне не подходит? Вот так получилась, что у меня примерно 80% иконок на сайте сделаны через фон, спрайтом. Вы мне предлагаете перепиливать шаблоны только для того, чтобы заменить png-спрайт на svg, если я могу просто поменять background в less?
          Плюс если иконки вставлять обычной картинкой — то каждая картинка это запрос к серверу. А, наскольк я помню, технология спрайтов была создана как раз для минимизации оных запросов. А мой вариант — это как раз замена спрайту.


          1. bolk
            13.05.2015 07:10
            +1

            Хосподи, чего там «перепиливать»-то? А технология спрайтов и png здесь каким боком? Мы, вроде, SVG обсуждаем. Хотя я и не понял вопроса, всё же отвечаю: загрузите SVG одним файлом, а потом используйте тег USE.


            1. Fayon Автор
              13.05.2015 09:19

              Хосподи, чего там «перепиливать»-то?

              Да, действительно, чего там перепиливать… Всего-то пару сотен иконок найти в шаблонах, повставлять инлайново, поправить цсс для каждого элемента. При том, что можно обойтись просто последним пунктом.


              1. bolk
                13.05.2015 09:28
                +1

                Вам же известно что на что менять, да? И вы программист?

                Я неоднократно был в ситуации «пара сотен замен по известному алгоритму», ну и что? И ещё буду в таких ситуациях. Вы лучше не распаляйтесь, а прислушайтесь.


  1. Fayon Автор
    13.05.2015 09:18

    Перенес комментарий в нужную ветку


  1. artemmalko
    13.05.2015 10:47

    Возможно вам стоит пересмотреть подход, так как svg-изображения в данном случае будут рендериться медленнее (настолько, что это становится заметно при кол-ве картинок > 50), чем использование стека svg (symbols) или инлайнового svg. И то, и то легко сделать плагинами для gulp.


    1. Fayon Автор
      13.05.2015 11:25

      С этим нюансом еще не сталкивался, проверю. Спасибо!


      1. artemmalko
        13.05.2015 12:32
        +2

        Тут можно прочесть подробнее.


        1. Fayon Автор
          13.05.2015 14:55

          Благодарю!


  1. ZimM
    13.05.2015 12:20

    Получается, svg дублируется в результирующем css столько раз, сколько используется миксин? Неэффективненько.


    1. artemmalko
      13.05.2015 12:31

      Не имеет значения, так как gzip.


  1. antirek
    13.05.2015 13:31

    Недавно сделал точно такую же штуку: заюзал svg чтобы цвет картинки в тон надписи менять.
    github.com/antirek/callButton.js/blob/master/callButton.js#L169

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


  1. autobusiness
    13.05.2015 16:25
    -1

    Вот сделано в css и все работает yabeda.by