[Перевод] Делаем разноцветные иконки с помощью SVG-символов и CSS-переменных
Давно прошли те дни, когда для иконок в вебе использовались картинки и CSS-спрайты. С развитием веб-шрифтов номером 1 для отображения иконок на сайтах стали иконочные шрифты.
Шрифты — векторные, так что вам не нужно беспокоиться о разрешении экрана. Для них можно использовать те же CSS-свойства, что и для текста. В результате вы имеете полный контроль над их размером, цветом и стилем. Вы можете добавлять к ним эффекты, трансформировать или декорировать их. Например, повернуть (rotate
), подчеркнуть (underline
) или добавить тень (text-shadow
).
Иконочные шрифты не идеальны, поэтому все большее число людей предпочитает использовать встроенные SVG-изображения. На CSS Tricks есть статья, где описаны моменты, в которых иконочные шрифты уступают SVG-элементам: резкость, позиционирование, сбои кросс-доменной загрузки, особенности браузеров и блокировщики рекламы. Сейчас вы можете обойти большинство этих проблем, что, в целом, делает использование иконочных шрифтов безопасным.
Да, еще одна вещь, которая абсолютно невозможна при использовании иконочных шрифтов: поддержка многоцветности. Только SVG может это сделать.
TL; DR: этот пост позволяет вникнуть в то, как и почему. Если вы хотите понять весь процесс, читайте дальше. В противном случае вы можете посмотреть окончательный код на CodePen.
Настройка символов SVG-иконок
Проблема встроенных SVG в том, что они сложны. Вы не хотите копипастить все эти координаты каждый раз, когда нужно использовать одну и ту же иконку. Получится повторяющийся, трудно читаемый и тяжело поддерживаемый код.
Использование SVG-символов позволяет иметь лишь один экземпляр каждого SVG-элемента и использовать его где угодно с помощью ссылки.
Начните с добавления встроенного SVG, спрячьте его, оберните содержимое в тег symbol
и задайте ему id
.
Полная разметка SVG-элемента пишется один раз и скрывается.
Затем все, что вам нужно сделать, это создать копию иконки с помощью элемента use
.
Получится точная копия вашей оригинальный SVG-иконки.
Вот она! Довольна милая, правда?
Вы вероятно заметили атрибут xlink:href
— это и есть ссылка между вашей иконкой и оригинальным SVG-изображением.
Важно отметить, что xlink:href
— устаревший атрибут SVG. Даже если большинство браузеров все еще поддерживает его, вместо него нужно использовать href
. Но дело в том, что некоторые браузеры, например, Safari, не поддерживают ссылки на SVG-ресурсы через атрибут href
, поэтому вам все равно нужно указывать xlink:href
.
Для безопасности используйте оба атрибута.
Добавление цвета
В отличие от шрифтов, свойство color
не действует на SVG-иконки: необходимо использовать атрибут fill
для указания цвета. Это значит, что они не наследуют родительский цвет текста, но вы все равно можете стилизовать их через CSS.
.icon {
width: 100px;
height: 100px;
fill: red;
}
А следовательно, вы можете создавать другие экземпляры этой же иконки разных цветов.
.icon {
width: 100px;
height: 100px;
}
.icon-red {
fill: red;
}
.icon-blue {
fill: blue;
}
Это работает, но это не совсем то, что мы хотим. Все, что мы сделали до сих пор, можно сделать и с помощью обычного иконочного шрифта. То, что мы хотим, — это сделать каждую часть иконки разного цвета. Мы хотим залить разными цветами каждую часть одной иконки, не изменяя другие ее экземпляры, и мы хотим, чтобы было возможно переопределять эти цвета при необходимости.
Сначала у вас может возникнуть идея положиться на специфичность.
.icon-colors .path1 {
fill: red;
}
.icon-colors .path2 {
fill: green;
}
.icon-colors .path3 {
fill: blue;
}
Это не сработает.
Мы пытаемся задать стили для .path1
, .path2
и .path3
так, если бы они были вложены в .icon-colors
, но технически это не так. use
элемент это не плейсхолдер, который заменяется на определенный SVG. Это ссылка, которая копирует содержимое того, на что указывает, в shadow DOM.
И что нам тогда делать? Как мы можем повлиять на содержимое детей, когда говорят, что детей нет в DOM?
CSS-переменные помогут
В CSS некоторые свойства наследуются детьми от предков. Если вы укажете цвет текста для body
, то весь текст на странице унаследует этот цвет, пока он не будет переопределен. Предок не знает детей, но наследуемые свойства все равно передаются.
В примере выше мы наследуем свойство fill
. Посмотрите еще раз и вы увидите, что класс, в котором мы определили этот цвет fill
добавляется к экземплярам иконки, а не к ее определению. Так мы смогли получить разноцветные копии одного источника.
Но вот проблема: мы хотим передать разные цвета для разных частей оригинальной SVG-иконки, но есть только один атрибут fill
, который мы можем наследовать.
Встречайте CSS-переменные.
CSS-переменные объявляются в наборах правил так же, как и любое другое свойство. Вы можете назвать их как хотите и присвоить им любое допустимое CSS значение. Затем вы определите через эту переменную значение свойства самого элемента или его ребенка, и оно будет наследоваться.
.parent {
--custom-property: red;
color: var(--custom-property);
}
Все дети .parent
будут иметь текст красного цвета.
.parent {
--custom-property: red;
}
.child {
color: var(--custom-property);
}
Все .child
, вложенные в .parent
, будут иметь текст красного цвета.
Теперь давайте применим эту концепцию для нашего SVG-символа. Мы будем использовать атрибут fill
для каждой части path
в определении нашей SVG-иконки и зададим им разные CSS-переменные. Затем мы назначим им разные цвета.
.icon-colors {
--color-1: #c13127;
--color-2: #ef5b49;
--color-3: #cacaea;
}
И… это работает!
С этого момента все, что нам нужно для создания копии с другой цветовой схемой, это написать новый класс.
.icon-colors-alt {
--color-1: brown;
--color-2: yellow;
--color-3: pink;
}
Если вы все еще хотите монохромную иконку, вам не нужно повторять один и тот же цвет для каждой CSS-переменной. Вместо этого вы можете определить одно правило для fill
: т.к. в этом случае CSS-переменные не определены, будет использоваться определение свойства fill
.
.icon-monochrome {
fill: grey;
}
Свойство fill
будет работать, потому что атрибут fill
исходного SVG задан с неопределенными значениями CSS-переменных.
Как назвать мои CSS-переменные?
Обычно используют один из двух способов именования в CSS: описательный или семантический. Описательный — это значит назвать переменную по названию самого цвета: если ваш цвет #ff0000
, вы называете переменную --red
. Семантический — это значит назвать переменную по ее назначению: если вы используете цвет #ff0000
для ручки чашки, вы называете переменную --cup-handle-color
.
Возможно вашим первым желанием будет использовать описательный способ наименований. Это кажется естественным, поскольку цвет #ff0000
может использоваться и для других вещей, помимо ручки чашки. CSS-переменную --red
можно использовать и для других частей иконки, которые должны быть красными. В конце концов, так работает методология utility-first CSS и она хороша.
Проблема в том, что в нашем случае мы не можем применять атомные классы к элементам, которые хотим стилизовать. Принципы utility-first не применимы, так как у нас есть только ссылка для каждой иконки, а мы должны стилизовать ее через вариации классов.
Использование семантического способа наименований, как например, --cup-handle-color
, в нашем случае более уместно. Когда вам нужно изменить цвет какой-то части иконки, вы точно знаете, что и как вам нужно переопределить. Имя класса останется актуальным независимо от того, какой цвет вы назначили.
По умолчанию или не по умолчанию
Заманчивая идея — сделать ваши иконки по умолчанию разноцветными. В этом случае вы сможете использовать их без дополнительной стилизации, и только в случае необходимости добавлять отдельный класс.
Добиться этого можно двумя способами: через : root или через var () default.
: root
Вы можете определить все ваши CSS-переменные в селекторе :root
. Это позволит держать их все в одном месте и «делиться» схожими цветами. :root
имеет самую низкую специфичность, поэтому его легко переопределить.
:root {
--color-1: red;
--color-2: green;
--color-3: blue;
--color-4: var(--color-1);
}
.icon-colors-alt {
--color-1: brown;
--color-2: yellow;
--color-3: pink;
--color-4: orange;
}
Однако, у этого метода есть существенные недостатки. Для начала, хранение определений цвета отдельно от соответствующих иконок может сбить с толку. Когда вы решите переопределить их, вам придется прыгать туда-обратно между селектором класса и :root
. Но, что еще более важно, этот метод не позволяет вам изменять ваши CSS-переменные, поэтому вы не можете повторно использовать одни и те же имена.
В большинстве случаев, когда в иконке используется только один цвет, я называю переменную --fill-color
. Это просто, понятно и позволяет использовать это наименование для всех одноцветных иконок. Если я определю все переменные в селекторе :root
, я не смогу иметь несколько переменных --fill-color
. Я буду вынуждена определять --fill-color-1
, --fill-color-2
или использовать пространства имен такие, как --star-fill-color
, --cup-fill-color
.
var () default
Функция var()
, которую вы используете для назначения CSS-переменной для свойства, может принимать значение по умолчанию в качестве второго аргумента.
Пока вы не определите --color-1
, --color-2
и --color-3
, иконка будет использовать значения по умолчанию, установленные для каждого path
. Это решает проблему глобального определения, которую мы имеем при использовании :root
, но будьте осторожны: теперь у вас есть значение по умолчанию, и оно выполняет свою работу. Таким образом, вы больше не можете написать только одно свойство fill
, чтобы сделать иконку монохромной. Вам нужно назначать цвет для каждой CSS-переменной, используемой в иконке, по отдельности.
Установка значений по умолчанию может быть полезна, но это компромисс. Я предлагаю не привыкать к этому, и делать только тогда, когда это имеет смысл для конкретного проекта.
Как это все поддерживается браузерами?
CSS-переменные поддерживаются всеми современными браузерами, но, как вы вероятно догадались, IE не поддерживает их, совсем. Даже IE11, и поскольку его развитие было прекращено в пользу Edge, нет никаких шансов, что он когда-либо будет их поддерживать.
Но только из-за того, что переменные не работают в браузере, который вам нужно поддерживать, не значит, что вы должны полностью от них отказаться. В таких случаях используйте graceful degradation: предлагайте разноцветные иконки современным браузерам, а для старых указывайте запасной цвет.
Все, что вам нужно сделать, — это добавить определение цвета, которое будет работать только, если CSS-переменные не поддерживаются. Этого можно добиться, указав свойству fill
резервный цвет. Если CSS-переменные поддерживаются, это объявление не будет учтено. Если же это не так, оно сработает.
Если вы используете Sass, вы можете записать эту проверку в @mixin
.
@mixin icon-colors($fallback: black) {
fill: $fallback;
@content;
}
Теперь можно определять цветовые схемы, не беспокоясь о поддержке браузеров.
.cup {
@include icon-colors() {
--cup-color: red;
--smoke-color: grey;
};
}
.cup-alt {
@include icon-colors(green) {
--cup-color: green;
--smoke-color: grey;
};
}
Передача CSS-переменных в mixin
через @content
необязательна. Если вы сделаете это снаружи, скомпилированный CSS будет таким же. Но может быть полезно держать все это в одном месте.
Вы можете проверить этот пример в разных браузерах. В последних версиях Firefox, Chrome и Safari последние две чашки будут соответственно красной с серым паром и синей с серым паром. В Internet Explorer и Edge ниже 15 версии третья иконка будет вся красная, а четвертая — вся синяя.