
Нам нужно:
- Установить на сайте собственные иконки с помощью SVG.
- Они должны управляться с помощью CSS (форма, размер, заливка, эффекты в том числе и их поведение).
- Они должны иметь маленький вес и находиться в одном месте для экономии http запросов.
- Работать во всех основных современных браузерах.
демо
Зачем я это пишу?
Несмотря на растущую популярность вектора в браузере, его возраст и поддержку браузерами, хороших решений использования, как кажется мне, очень мало. Позвольте объясниться. Конечно о 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.
Вот и все, наша система готова. Да, она упрощенна для этого примера. В моем проекте, я бы задавал цвет для разных иконок и соответственно поведения через отдельный класс. Ведь иконки у нас могут быть разных цветов и поведение может быть разным (не все же иконки серые). Но не забивайте этим голову, давайте сейчас держать вещи простыми.
Чтобы добавить новую иконку в нашу систему, нужно всего лишь:
- Задекларировать новый clip-path c формой иконки и своим id в общем SVG документе (в разделе <defs>).
- Создать новый класс для формы иконки в 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 в свою очередь не очень дружит с внешними источниками. Им нужен внутренний.
Разводим миры по углам.
В смысле, мирим. То есть, заставляем водить хоровод.
Да, речь пойдет о кроссбраузерности.
Итак нам нужно:
- Два CSS файла с разными путями для двух разных миров.
- Редактировать и поддерживать стили иконок в одном месте(файле).
- Иметь один источник декларации форм иконок и фильтров — один SVG документ.
- Не задумываться об этом в дальнейшем при разработке.
Приступим. В решении этих задач нам помогут препроцессоры. В качестве 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 байт)
- Все иконки лежат в одном файле, так что мы не будем сервер доставать запросами.
Все задачи выполнены. Здесь с Вашего позволения я и остановлюсь. Всем большое спасибо за внимание и время, надеюсь, Вы провели его с пользой.
