SVG-иконки – много и со стилем
Маленький рассказ о том, как наша команда решила организовать иконки в грядущем проекте. Чуть-чуть исторического экскурса, взгляды по сторонам (на PNG и векторные шрифты) и рассказ о том, как мы всё-таки обустроились в итоге.
Иконки у нас используются, и активно – хорошо подобранная иконка заменяет слова и предложения (а фигово подобранной иконке можно сделать всплывающую подсказку, но не будем о грустном)
- хочется вектора. Ну, ладно, вектор – это средство, а не цель. Цель – не беспокоиться ВООБЩЕ об изменении размеров, ретина дисплеях, сохранении изображения в разных форматах для разных целей.
- хочется стилизации иконок. Потому что у нас из коробки как минимум два набора тем (светлая и тёмная), а то и контрастная, для людей с нестандартным зрением, а то и оранжевенькая какая-нибудь появится ближе к Новому году… В общем – одна и та же по сути иконка должна выглядеть слегка иначе в зависимости от выбранной на странице темы.
- хочется динамической стилизации иконок. Статики – нам мало. Этого хватает для скриншотиков и рекламных буклетиков, но не для живых пользователей. А мы хотели жизни! Мы хотели ховера! Мы хотели селекшена. И дизаблить, дизаблить их всех. Извините.
- НЕ хочется, чтобы в этом участвовал JavaScript в любой его форме и проявлении. Иконки – это внешний вид, а за него ответственный HTML + CSS. Ну, ладно, класс selected я готов навесить на элементы, но это последняя граница…
tl;dr Внимание. Следующие несколько разделов – это расплывание мыcлею по древу, причём вширь, обзор решений (в том числе – неудачных) и котик в разных ракурсах. Кому хочется технических подробностей того, что же вышло в итоге – пожалуйте сюда.
Чем не удовлетворяли классические решения
- Мы можем сделать тучу png-иконок и класть их внутрь тэгов image. И подменять на JS-событиях наведения мыши. Когда я делал сайт на третьем курсе, этот метод реально работал. Хорошо было, и не надо было заботиться о множестве соединений с сервером. Может, когда круг замкнётся и HTTP 2.0 победит… а пока это накладно. Поехали дальше.
- PNG-спрайты + CSS/background-image – хорошо, но не хватает. Наши коллеги из DevExtreme жили так некоторое время… но упёрлись в необходимость хоть какой-нибудь стилизации. Ведь с PNG-иконкой беда, её даже в красный на клиенте не покрасишь! Они перешли на Font Icons, а мы.
- Font Icons для написания приложения (не большой библиотеки, а именно приложеньица) оказался слегка неудобным в работе. Изменения иконок требуют некоторого обслуживания (сбор иконок, сохранение в файл шрифта, хинтинги всякие…) Если в интернете уже есть все нужные вам иконки в составе Font Awesome – лучше варианта и искать не стоит. Но если иконки нужны свои… мы пока пошли дальше.
- Unicode-символы для иконок. Сам пошутил, сам посмеялся.
А давайте положим SVG в ДОМ?
Мы, конечно, можем это сделать. Скопипастить каждую иконку и вставить её туда, где она будет нужна, в каждый кусочек HTML-разметки.
Сам котик лежит тут. То есть вот это всё предлагается положить внутрь документа.
А если она поменяется, то перескопипастить во все места использования. Или поднапрячься и написать для нашего проекта узкоспециальное решение, которое во время сборки, или во время деплоймента, или во время исполнения будет по магическим атрибутам понимать, что вот тут должна быть иконка, и инжектить (inject, не знаю, как перевести) содержимое одного файлика внутрь содержимого другого файлика. Это очень весело звучит и очень напрягает в поддержке.
К сожалению или к счастью, нам в DevExpress этот путь не подходит по определению. Мы не пишем (почти) конечные программные решения, мы пишем то, что другие люди будут использовать в своих решениях. Представив себе лица этих людей, которых бы мы попросили ручками инжектить наши SVG на их странички, мы резко перешли к следующему возможному решению…
А если положить в DOM, но чуть-чуть, а лучше – автоматически?
Ну то есть подсоединить к нашему документу некое хранилище, аналог спрайта с иконками, в котором лежали бы всеее иконки.
А там, где они нужны, мы бы сказали – а дай-ка нам, дорогой браузер, иконку kotik!
Да, так можно делать, и люди так делают. Собираем все наши иконки пофайлово и кладём их все внутрь одного большооого SVG-спрайта, который содержит в себе лишь шаблоны иконок. Технически для этого используется элемент symbol.
Результат оборачивания кота в символ плагином gulp-svgstore. Собственно, почти ничего и не поменялось.
Этот элемент нигде не отображается – by design. Он всего лишь маска для будущих настоящих иконок. Мы можем взять этот шаблон и положить его на страницу, сославшись на него из элемента use.
А ещё можем сделать интереснее. Шаблон – он уже дома. В смысле в DOMе. И его уже можно стилизовать совсем как настоящий. Используя имена svg-элементов ( path , circle , rect и т.д.), мы можем применять к ним CSS-правила и модифицировать атрибуты (цвета через fill и stroke , толщины и стиль линий)
И теперь все коты, ссылающиеся на этот шаблон, станут оранжевохвосты.
Так, наш последний метод уже дал нам два из трёх пунктов наших хотелок. А вот при попытке застилизовать одну конкретную иконку меня сначала ждало разочарование.
Удивиться истории про use, xlink: href и деревья вместе
Как только мы пытаемся кастомизить одного взятого котика в лоб – всё перестаёт работать.
Увы, хвост Барсика не рыжеет. Причина этого интересна. Барсика на самом деле там нет. Барсик – в тени.
ДОМ теней
Shadow DOM – штука уже как бы не новая. Её начали использовать производители браузеров для того, чтобы слепить input type=datebox, а мы об этом и не знали. В тот момент они ещё не были в курсе, что используют Shadow DOM, имя и форму он обрёл несколько позже…
Элемент use – в современных терминах – использует именно что Shadow DOM.
Элементы, которые мы *клонируем* внутрь *корня* use – помещаются в наш DOM… но не до конца. В частности, к ним нельзя применить CSS правила.
Но корень теневого дерева, элемент <use /> – он как бэ с одной стороны в нашем основном дереве, а с другой – является предком всего, что в дереве лежит. С первой стороны мы применим к нему CSS-стиль. Со второй же стороны – он пробросит приданные ему CSS-свойства своим потомкам. Если мы скажем шаблону, чтобы он к этому прислушался.
Так Барсик приобретает оранжевый окрас, а Васька — дымчато-белый. (если частям и элементам его кошачьего SVG-тела не указано иного. Приоритет наследованных CSS-свойств довольно низок, ниже атрибутов и уж точно ниже других правил, примененных к темплейту – а темплейт живёт полностью в DOM, без всяких оговорок. Подробности чуть ниже) А дальше мы можем уже использовать любые вариации.
Таким образом, один цвет мы уже можем кастомизить совершенно свободно и в полном соответствии с буквой CSS. Реализовать подсветку при наведении становится проще простого.
Порядок применения стилей, свойств и атрибутов
Подход первый, без CSSУ SVG-элемента может быть атрибут, указывающий его цвет. Для большинства фигур (path, circle, ellipsis) это fill, реже используется stroke (для указания цвета границ фигуры).
Если у элемента этого атрибута нет – он пытается получить его у предков. Идёт вверх по своим родителям до тех пор, пока не находит кого-то с указанным атрибутом.
Два круга, заливка не указана, берут у родителя. Очень удачно – у первого же родителя, группы, в которой они лежат, есть заливка. Оба круга становятся синими.
Подход второй. Появление стилизацииКак только мы применяем стили – они побеждают. Правило
Сделает оранжевыми оба круга. Оно просто сильнее…
Подход третий. Стилизация убивает атрибуты, чтобы элементы использовали атрибутыВот такой код даст нам фиолетовый эллипс. Однако добавление CSS-правила
Заставит его забыть собственный цвет и начать брать его у предков. Вот так выглядит пример целиком. Любые совпадения случайны. Эллипс станет оранжевым.
В общем-то, на этом трюке и основана большая часть примеров. Ещё могут быть inline-стили и !important, но там уже всё идёт по аналогии
Ну и ещё немножечко магии
Стилизация второго цвета
С помощью хакотрюка, основанного на переменной SVG currentColor, мы можем кастомизить уже два цвета:
Трюк с невидимым котом
Ещё одна минорная вещь, которая, в принципе, может когда-то пригодиться — скрывать элементы изображения при некоторых условиях.
Напрямую мне этого добиться не удалось — у опции visibility так, всё или ничего… Но мне удалось, с помощью тех же цветов, покрасить элемент в прозрачный цвет. Очень красиво, главное — правильно подобрать оттенок.
Технические мелочи
SVG-спрайт должен лежать в DOMe. Большинство браузеров смогли бы показать шаблоны и из залинкованного файла, но не ИЕ. Поэтому – выкачиваем через AJAX и вставляем в ДОМ. Есть статья.
Движки шаблонов могут работать странно. Атрибут xlink:href живёт в своём неймспейсе (собственно, в неймспейсе xlink, там так и написано), и это не все любят. Например, Knockout не умеет это биндить из коробки. Есть воркараунд. Он работает.
Могу предположить, что в других движках темплейтов могут возникнуть схожие проблемы. Могу предположить, что их можно решить схожим образом.
Что же получилось в итоге
- Наши иконки хранятся и лежат в отдельных SVG файликах, маленьких, любимых в VCS, понятных в диффах и открываемых в браузере
- Наши иконки в момент билдёжки собираются внутрь одного большого файлика – icons.svg. То, что было файликом, становится шаблоном.
- Этот файлик нужно положить внутрь HTML-разметки. Руками, инструментом сборки или JavaScript
- На иконки можно ссылаться с помощью вот такой конструкции:
Работает в IE9+, вебкитах, на читалке Amazon Kindle и на телевизоре Samsung.
Все примеры стилизации SVG-иконок лежат на Гитхабе. Я давал ссылки на GitHub Pages этого репозитория. Надеюсь, кого-то заинтересовал этот рассказ.
UPD Добавил по результатам вопроса в комментариях ещё одну страничку, показывающую стилизацию 122-х настоящих иконок, всего 500 экземпляров на странице Иконки для этого примера взяты на www.flaticon.com
Благодарности
При подготовке блог-поста не пострадало ни одно животное. Котик, изначально растровый, был переведён в СВГ-термины в онлайн-редакторе (исходники).
Навык рисования котиков я получил, рисуя котиков своему сыну, который был ими чрезвычайно доволен. Претензии же по поводу несимметричности ушей я принимать отказываюсь.