Делаем разноцветные иконки с помощью SVG-символов и CSS-переменных

https://medium.freecodecamp.org/lets-make-your-svg-symbol-icons-multi-colored-with-css-variables-cddd1769fca4
  • Перевод
Icons

Давно прошли те дни, когда для иконок в вебе использовались картинки и CSS-спрайты. С развитием веб-шрифтов номером 1 для отображения иконок на сайтах стали иконочные шрифты.

Шрифты — векторные, так что вам не нужно беспокоиться о разрешении экрана. Для них можно использовать те же CSS-свойства, что и для текста. В результате вы имеете полный контроль над их размером, цветом и стилем. Вы можете добавлять к ним эффекты, трансформировать или декорировать их. Например, повернуть (rotate), подчеркнуть (underline) или добавить тень (text-shadow).

Иконочные шрифты не идеальны, поэтому все большее число людей предпочитает использовать встроенные SVG-изображения. На CSS Tricks есть статья, где описаны моменты, в которых иконочные шрифты уступают SVG-элементам: резкость, позиционирование, сбои кросс-доменной загрузки, особенности браузеров и блокировщики рекламы. Сейчас вы можете обойти большинство этих проблем, что, в целом, делает использование иконочных шрифтов безопасным.

Да, еще одна вещь, которая абсолютно невозможна при использовании иконочных шрифтов: поддержка многоцветности. Только SVG может это сделать.

TL;DR: этот пост позволяет вникнуть в то, как и почему. Если вы хотите понять весь процесс, читайте дальше. В противном случае вы можете посмотреть окончательный код на CodePen.


Настройка символов SVG-иконок


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

Использование SVG-символов позволяет иметь лишь один экземпляр каждого SVG-элемента и использовать его где угодно с помощью ссылки.

Начните с добавления встроенного SVG, спрячьте его, оберните содержимое в тег symbol и задайте ему id.

<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
  <symbol id="my-first-icon" viewBox="0 0 20 20">
    <title>my-first-icon</title>
    <path d="..." />
  </symbol>
</svg>

Полная разметка SVG-элемента пишется один раз и скрывается.

Затем все, что вам нужно сделать, это создать копию иконки с помощью элемента use.

<svg>
  <use xlink:href="#my-first-icon" />
</svg>

Получится точная копия вашей оригинальный SVG-иконки.

Black cup

Вот она! Довольна милая, правда?

Вы вероятно заметили атрибут xlink:href — это и есть ссылка между вашей иконкой и оригинальным SVG-изображением.

Важно отметить, что xlink:href — устаревший атрибут SVG. Даже если большинство браузеров все еще поддерживает его, вместо него нужно использовать href. Но дело в том, что некоторые браузеры, например, Safari, не поддерживают ссылки на SVG-ресурсы через атрибут href, поэтому вам все равно нужно указывать xlink:href.

Для безопасности используйте оба атрибута.

Добавление цвета


В отличие от шрифтов, свойство color не действует на SVG-иконки: необходимо использовать атрибут fill для указания цвета. Это значит, что они не наследуют родительский цвет текста, но вы все равно можете стилизовать их через CSS.

<svg class="icon">
  <use xlink:href="#my-first-icon" />
</svg>

.icon {
  width: 100px;
  height: 100px;
  fill: red;
}

А следовательно, вы можете создавать другие экземпляры этой же иконки разных цветов.

<svg class="icon icon-red">
  <use xlink:href="#my-first-icon" />
</svg>
<svg class="icon icon-blue">
  <use xlink:href="#my-first-icon" />
</svg>

.icon {
  width: 100px;
  height: 100px;
}
.icon-red {
  fill: red;
}
.icon-blue {
  fill: blue;
}

Это работает, но это не совсем то, что мы хотим. Все, что мы сделали до сих пор, можно сделать и с помощью обычного иконочного шрифта. То, что мы хотим, — это сделать каждую часть иконки разного цвета. Мы хотим залить разными цветами каждую часть одной иконки, не изменяя другие ее экземпляры, и мы хотим, чтобы было возможно переопределять эти цвета при необходимости.

Сначала у вас может возникнуть идея положиться на специфичность.

<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
  <symbol id="my-first-icon" viewBox="0 0 20 20">
    <title>my-first-icon</title>
    <path class="path1" d="..." />
    <path class="path2" d="..." />
    <path class="path3" d="..." />
  </symbol>
</svg>
<svg class="icon icon-colors">
  <use xlink:href="#my-first-icon" />
</svg>

.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-переменные. Затем мы назначим им разные цвета.

<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
  <symbol id="my-first-icon" viewBox="0 0 20 20">
    <title>my-first-icon</title>
    <path fill="var(--color-1)" d="..." />
    <path fill="var(--color-2)" d="..." />
    <path fill="var(--color-3)" d="..." />
  </symbol>
</svg>
<svg class="icon icon-colors">
  <use xlink:href="#my-first-icon" />
</svg>

.icon-colors {
  --color-1: #c13127;
  --color-2: #ef5b49;
  --color-3: #cacaea;
}

И… это работает!

Multi-color cup

С этого момента все, что нам нужно для создания копии с другой цветовой схемой, это написать новый класс.

<svg class="icon icon-colors-alt">
  <use xlink:href="#my-first-icon" />
</svg>

.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-переменной для свойства, может принимать значение по умолчанию в качестве второго аргумента.

<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
  <symbol id="my-first-icon" viewBox="0 0 20 20">
    <title>my-first-icon</title>
    <path fill="var(--color-1, red)" d="..." />
    <path fill="var(--color-2, blue)" d="..." />
    <path fill="var(--color-3, green)" d="..." />
  </symbol>
</svg>

Пока вы не определите --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 версии третья иконка будет вся красная, а четвертая — вся синяя.
Поделиться публикацией

Похожие публикации

Комментарии 18
    +2
    В отличие от шрифтов, свойство color не действует на SVG-иконки: необходимо использовать атрибут fill для указания цвета.

    Иногда полезнее сделать fill: currentColor глобально и раскрашивать через color родителя.

      0
      Если иконок много то спрайт будет слишком толстый, если на странице используются 1-5 иконок, и ради них тянуть спрайт со остальными 70 иконками… так себе решение.
      А чтобы заставить это работать в осле, надо js еще подключать, который будет брать иконку из спрайта и инжектить в html. Но если осел не нужен и класть на размер спрайта, то вполне неплохое решение)
        0
        Речь идет об инлайновых SVG-иконках, без спрайтов. Прочитайте, пожалуйста, статью внимательнее и посмотрите пример на CodePen
          0
          Это тоже самое, только так называемый спрайт у вас присутствует в теле html а не во внешнем файле.
          При таком подходе мне не понятно, зачем вообще делать <use xlink:href="#icon-coffee" href="#icon-coffee" /> Если можно сразу вставить svg код в нужное место, и без извращений поменять цвет, рамер… да что угодно. Это железно работает везде.

            0
            Полагаю, это делается для избежания дублирования инлайновых SVG, но SVG-спрайты сильно некроссбраузерны.
              0
              Согласен. Но мне кажется что это усложняет изменение стилей у каждой иконки.
                0
                Все довольно кроссбраузерно. Ничего сложного в том чтобы создавать такие спрайты нет, даже вручную, в этом просто стоит раз нормально разобраться.
                Уже 3 года моя команда пользуется спрайтами и всегда работает во всех браузерах даже ie 10.
                Это не спойлер но если лень делать руками, есть хорошая платформа для генерации таких спрайтов — icomoon. И все ваши нарекания сразу отпадают :)
                  0
                  Глубоко я действительно не разбирался, но раз вы разбирались, просветите, как обеспечивается работа вот этого:

                  <svg viewBox="0 0 100 100">
                     <use xlink:href="defs.svg#icon-1"></use>
                  </svg>
                  


                  в IE11? Если так как описал x07 выше, то так себе это кроссбраузерность.
                    0
                    Для того чтоб в ослах это работало нужен js. Вот тут есть все необходимое css-tricks.com/svg-use-external-source

                    В статье еще используются css переменные, которые так же не везде поддерживаются. Это как бы тоже уже не кроссбраузерно. Осла списывать рановато
          +2
          А еще помните, что инлайновые иконки создают дополнительное DOM-дерево, доступное для модификации(что медленнее, чем вставка их через src), а уж тяжелая стилизация, особенно раскрашивание отдельных частей иконок CSS-ом может привести к существенным тормозам, когда число движущихся частей в приложении и так большое.

          Столкнулся с этим в реальности, сидел и заменял описанное в статье на растровые спрайты, чтобы polymer-quill не тормозил.
            0
            Спасибо, интересно. Жаль, нет способа заставить старые версии браузеров отображать иконки в нужных цветах. Но для одноцветных иконок — прекрасное решение!
              0
              хорошие иконки. а есть ссылка на них?
              0

              Здесь пытаются добавить поддержку CSS-переменных в IE11 и выше.

                0
                К сожалению SVG use достаточно странная конструкция, которая с одной стороны — изолирует вложенное содержимое, и его сложно стилизовать. А с другой стороны нет — стили, определенные внутри SVG, без проблем «текут» из изображение в изображение.
                Есть и другая проблема — заливки (defs) не поддерживаются от слова вообще.

                Вот хороший пример — codesandbox.io/s/k26937poxr, достаточно просто потыкать мышкой в html код, чтобы увидеть глубину падения.
                  0
                  применять атомные классы к элементам

                  атомарные?

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

                  Самое читаемое