Нам нужно:
  1. Установить на сайте собственные иконки с помощью SVG.
  2. Они должны управляться с помощью CSS (форма, размер, заливка, эффекты в том числе и их поведение).
  3. Они должны иметь маленький вес и находиться в одном месте для экономии http запросов.
  4. Работать во всех основных современных браузерах.

демо

Зачем я это пишу?


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

Сразу скажу да, я использовал иконочные шрифты, вот в чем здесь проблемы:
1. Такие иконки в браузере рендерятся как шрифт и в Windows, например, часто получаются мыльные края. Есть CSS свойства, которые должны решать эту проблему, но они работают только в WebKit и только под MAC — то есть бесполезны. Дизайнер ругался.
2. Только в 23 Chrome и только под Windows такой шрифт начал исчезать, а в некоторых случаях сильно «рвать» остальную верстку сайта. Я много раз пользовался такими шрифтами, но первый раз со своими собственными иконками. И первый раз такая проблема.
3. Невозможно добавлять внутреннюю тень. В проекте это было обязательно. Дизайнер ругался.
4. Вес такого шрифта. C SVG не сравнится.
5. Все-таки SVG имеет больше возможностей по сравнению со шрифтом.

Приступим или исходные данные.


Исходные данные — чиcтый HTML документ с подключенным к нему main.css или то место, где мы будем писать стили наших иконок.
Так же добавим SVG документ в тег body. В нем, в разделе defs, мы будем декларировать формы наших иконок в виде clip-path и фильтры для них (внутренняя тень).
Как декларировать SVG лучше подсмотреть у вредного старичка, так как он самый превередливый.

<!doctype html>
<html>
  <head>
    <meta charset=utf-8 />
    <title></title>
    <link rel="stylesheet" href="main.css">
  </head>

  <body>

    <?xml version="1.0" standalone="no"?>
    <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
      <svg version="1.1" xmlns="http://www.w3.org/2000/svg">
         <defs>

           <!-- здесь будут декларироваться формы наших иконок и внутренние тени к ним -->

         </defs>
     </svg>

  </body>
</html>


Добавляем форму иконки


Как получить SVG код, я думаю, писать не нужно? Достаточно просто открыть SVG файл в текстовом редакторе или воспользоваться любым векторным редактором, например Illustrator. Для наглядности здесь я показываю только SVG код из нашего HTML документа (помните, что он находится в теге body). Создаем тег clipPath и добавляем в него SVG форму иконки.

<?xml version="1.0" standalone="no"?>
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg version="1.1" xmlns="http://www.w3.org/2000/svg">
    <defs>


     <clipPath id="heart-path">

          <path fill-rule="evenodd" clip-rule="evenodd" d="M256,512c0,0-256-144.938-256-311.694C0,29.22,240.62,10.145,256,192 c18.467-181.721,256-162.784,256,8.306C512,367.062,256,512,256,512z"/>

     </clipPath>


     </defs>

 </svg>


Мы создали путь для иконки в виде сердца. Прошу обратить внимание на аттрибут id="heart-path" у тега clipPath.
Через него мы будем ссылаться на форму нашей иконки.

Добавляем иконки


Вставляем в HTML документ нашу иконку. Она состоит из маленького SVG документа, внутрь которого вставлен квадрат:

 <svg class="heart-icon icon" viewBox="0 0 512 512">
      <rect width="512" height="512" />
 </svg>

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

Обратите внимание на цифру 512. Это размер квадрата в который была вписанна иконка, когда рисовалась в редакторе. С Вашей иконкой может быть иначе. Класс heart-icon для задания формы иконки в CSS, класс icon — объединяющий класс для всех иконок (их же будет масса?).

Последний шаг. Добавляем CSS


Давайте посмотрим что получилось. Если Вы все сделали правильно, то увидите черный квадрат 512 на 512 пикселя. ��алевич уже одобряет. Но, боюсь, наш с вами манифест супрематизма никто уже не оценит. Продолжаем.

Все правильно, иконок нет? Приступаем к самому интересному — CSS.

В main.css который мы подключили к документу пишем стили для классов heart-icon и icon.

.icon{
  width:32px;
  height:32px;
  cursor:pointer;
  fill: #ccc;
}
Наш черный квадрат становится серым и 32х32 пикселя. Свойство fill задает заливку нашей иконки.

.heart-icon rect{
  clip-path:url('#heart-path');
}
Наконец-то появляется форма иконки! clip-path именно то cвойcтво, которое заставляет браузер брать форму по #heart-path и применять ее к квадрату.

Добавим поведение для иконки. Это будут :hover, :active состояния и checked класс.

.heart-icon rect{
  clip-path:url('#heart-path');
}

.icon:hover{
  fill: #999;
}

.icon:active, .icon.checked{
  fill:red;
}



Для наглядности я скопировал иконку и добавил ей класс checked.
Вот и все, наша система готова. Да, она упрощенна для этого примера. В моем проекте, я бы задавал цвет для разных иконок и соответственно поведения через отдельный класс. Ведь иконки у нас могут быть разных цветов и поведение может быть разным (не все же иконки серые). Но не забивайте этим голову, давайте сейчас держать вещи простыми.

Чтобы добавить новую иконку в нашу систему, нужно всего лишь:

  1. Задекларировать новый clip-path c формой иконки и своим id в общем SVG документе (в разделе <defs>).
  2. Создать новый класс для формы иконки в CSS.

Так просто.

Я добавил иконку плей так как код ее очень простой и хорошо иллюстрирует, что в качестве clip-path можно использовать не только пути, но и любые фигуры (которые поддерживает SVG конечно).

Добавляем внутренюю тень


Для этого мы будем использовать SVG filter.
Сразу предупреждаю. Для данного примера у меня возникали небольшие трудности с иконками при использовании фильтра поверх них. Часть из них иногда исчезала. Иногда. Может только у меня. Будьте на чеку. И еще одна проблема — чтобы фильтр накладывался красиво нужно добавить еще один элемент в иконку. Да, очень жаль. Теперь иконка с фильтром = 3 элемента. Так что если Вам нужно воплатить трендовый флет дизайн, смело перематывайте.

Добавим элемент:
 <svg class="heart-icon icon" viewBox="0 0 512 512">
    <g>
      <rect width="512" height="512" />
    </g>
 </svg>
Тэг <g> и есть тот самый элемент, который используется для фильтра.

Добавляем в раздел SVG документа фильтр:
<filter id='inset-shadow'>
    
    <!-- Сдвиг тени -->
    <feOffset
      dx='0'
      dy='0'
    />
    
    <!-- Размытие тени -->
    <feGaussianBlur
      stdDeviation='20'
      result='offset-blur'

    />
    
    <!-- Инвертируем drop shadow
         чтобы создать внутреннюю тень -->
    <feComposite
      operator='out'
      in='SourceGraphic'
      in2='offset-blur'
      result='inverse'
    />
    
    <!-- Цвет и Прозрачность -->
    <feFlood
      flood-color='black'
      flood-opacity='.65'
      result='color'
    />
    
    <!-- Обрезаем цвет внутрь тени -->
    <feComposite
      operator='in'
      in='color'
      in2='inverse'
      result='shadow'
    />
    
    <!-- Раcполагаем тень поверх элемента -->
    <feComposite
      operator='over'
      in='shadow'
      in2='SourceGraphic'
    />
  </filter>


Описывать как работает фильтр, я не стану. Вы запросто прочитаете это в интернете. Это ведь тема для отдельного поста. Просто покрою его комментариями.
Обратите внимание что у фильтра тоже есть id='inset-shadow'

Добавляем к иконкам тень
.icon g{
  filter:url('#inset-shadow')
}

Вот и все. Хочется мне написать. Но это было бы неправдой.

Правда или любим всех


Дело в том, что если вы откроете пример в Opera, то увидите набор из 3 квадратов. Те иконки, что с тенью и вовсе исчезают и это проблема SVG фильтров. Если браузер не находит нужный фильтр, он рендерит иконку прозрачной, вместо того, чтобы просто его не применять.

Но почему же не находит?! Дело в том, что здесь сталкиваются 2 браузерных мира. Наш путь url('#heart-path') Opera воспринимает как путь относительный файла CSS или url('main.css#heart-path') вместо url('index.html#heart-path'), как делают это остальные. Если задать путь эксклюзивно как url('index.html#heart-path'), то браузеры не парсят SVG документ внутри index.html, так как считают его внешним источником. Такие же проблемы возникают и у Mozilla, как только Вы переносите main.css за пределы каталога с файлом index.html. IE же в этом вопросе солидарен с WebKit. А WebKit в свою очередь не очень дружит с внешними источниками. Им нужен внутренний.

Разводим миры по углам.


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

Итак нам нужно:
  1. Два CSS файла с разными путями для двух разных миров.
  2. Редактировать и поддерживать стили иконок в одном месте(файле).
  3. Иметь один источник декларации форм иконок и фильтров — один SVG документ.
  4. Не задумываться об этом в дальнейшем при разработке.

Приступим. В решении этих задач нам помогут препроцессоры. В качестве CSS препроцессора в данном примере я буду использовать Stylus, который незаслуженно обделен вниманием русскоязычного сообщества разработчиков, но максимально просто и наглядно проиллюстрирует данный пример.
В качестве HTML препроцессора я буду использовать PHP, самый массовый препроцессор и серверный язык одновременно. Любой разработчик, пользовавшийся когда-нибудь препроцессором запросто ��апишет этот пример для своего любимого инструмента. Главное принцип.

Давайте переименуем файл index.html в index.php. После этого создадим папку /css и разместим в ней файл icons.svg, куда перенесем наш большой SVG документ с декларациями форм иконок.



На месте большого SVG документа в index.php напишем PHP выражение
<?php include "css/icons.svg"; ?>
которое включает текст файла icons.svg на то место, где встречается само выражение.
Пункт 3 выполнен, можно вычеркивать.

Теперь пункт 1 и 2.

В каталоге /css создадим файл icons.styl. Это будет именно то одно место, где мы будем редактировать стили наших иконок. Переместим в него все содержимое файла main.css и оформим в виде миксина icons_mixin:
icons_mixin( path = '' ) 

     .heart-icon rect{
          clip-path:url( path + '#heart-path');
     }

     .play-icon rect{
          clip-path:url( path + '#play-path');
     }


     .icon{
          width:32px;
          height:32px;
          cursor:pointer;
          fill: #ccc;

          g{
               filter:url( path + '#inset-shadow')
          }

          &:hover{
               fill: #999;
          }

          &:active, &.checked{
               fill:red;
          }
            
     }

Миксин принимает в качестве параметра путь к SVG формам. Его мы будем использовать при формировании путей для разных браузеров.
CSS стили иконок нисколько не изменились, я просто добавил нестинг для удобства и наглядности примера.

Теперь создадим еще 2 .styl файла. webkit_ie.styl и ff_op.styl.
Первый будем использовать на нашем вебсайте по умолчанию, второй только для Mozilla и Opera.

В файл webkit_ie.styl добавим:
@import 'icons.styl'

icons_mixin()

Импортируем файл с миксином иконок и выполняем его без параметров.
В файл ff_op.styl добавим:
@import 'icons.styl'

icons_mixin('icons.svg')

Импортируем файл с миксином иконок и передаем ему путь к icons.svg.

Пункт 1 и 2 выполнены. Вычеркиваем.

Если запутались, вот так выглядит проект:


В index.php поправим путь к стилям иконок, теперь это
<link rel="stylesheet" href="css/webkit_ie.css">

И в самом конце, перед закрывающимся тегом </body> добавим скрипт для условного добавления стилей иконок для браузеров с движком отличным от WebKit или Trident(IE):

<script type="text/javascript">

var firefox = navigator.userAgent.indexOf("Firefox") != -1 ;
var opera = navigator.userAgent.indexOf("Opera") != -1 ;

if ( firefox || opera ) {
  document.write('<link rel="stylesheet" href="css/ff_op.css">');
}

</script>

Вот и все. исходники

Что же мы наделали!?


Давайте оглянемся назад и посмотрим, что натворили. Несмотря на то, что с первого взгляда, система может показаться слишком комплексной, мне кажется, она получилась достаточно гибкая и простая (с моего второго взгляда) чтобы иметь право на жизнь.
  • Она полностью управляется с помощью CSS.
  • Она может работать в
    • IE 9-10
    • Mozilla 4+
    • Opera 11.6+
    • Safari 5.1+
    • Chrome 14+ (я думаяю и 4+, но проверял только до этой версии)
  • Она достаточно легка по весу. (Вспомним иконку плей <polygon fill-rule=«evenodd» clip-rule=«evenodd» points=«0,0 512,256 0,512» /> вес ее составляет всего 85 байт)
  • Все иконки лежат в одном файле, так что мы не будем сервер доставать запросами.

Все задачи выполнены. Здесь с Вашего позволения я и остановлюсь. Всем большое спасибо за внимание и время, надеюсь, Вы провели его с пользой.