04.08.2015

Стилизуем содержимое SVG с помощью CSS

Одним из самых распространенных примеров использования SVG графики являются системы иконок. Для этого используется SVG элемент <use>, задающий экземпляры (копии) иконок. И при попытке стилизации этих экземпляров возникает куча ограничений. Целью данной статьи как раз является ознакомление с возможными способами обхода этих ограничений при использовании <use>.

Но перед тем как начать, давайте пробежимся по основам SVG - это постепенно втянет нас в мир use и теневого DOM, а затем вернемся к CSS. Мы поймем, почему стилизация <use> является сложной и рассмотрим пути решения этой проблемы.

Быстрый обзор структуры SVG, группировки и связывания (переиспользования) элементов в SVG.

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

Вот эти элементы: <g>, <defs>, <use> и <symbol>.

Элемент <g> (короткое от "group") используется для логической группировки набора связанных графических элементов. С точки зрения графических редакторов, таких как Adobe Illustrator, элемент <g> имеет ту же функциональность, как и Group Objects. Можно также представлять группу как слой в графическом редакторе.

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

Элемент <defs> применяется для определения элементов, которые будут использоваться позднее. Определение элементов через <defs> полезно, когда вы хотите создать своего рода "шаблон", который будет использоваться несколько раз в документе. Элементы, определенные внутри <defs> не отображаются, пока не будут "вызваны" где-то в документе.

Элемент <symbol> сочетает в себе преимущества и <defs> и <g>, т.к. может использоваться в качестве группировки элементов, определяющих шаблон, на который можно ссылаться из любого места документа. В отличие от <defs>, <symbol> как правило, не используются для определения шаблонов, а в основном используется, чтобы определить символы (например иконки), на которые имеются ссылки по всему документу.

Элемент <symbol> имеет важное преимущество: он принимает атрибут viewBox, который позволяет масштабироваться внутри любого вьюпорта, в котором происходит отрисовка.

Элемент <use> используется для ссылки на другие элементы, определенные где-то в документе. Он позволяет повторно использовать существующие элементы, давая вам возможность, аналогичную копированию-вставке в графическом редакторе. Он может применяться для переиспользования как отдельного элемента, так и группы, заданной через <g>, <defs> или <symbol>.

Для использования элемента передаем ссылку на него внутри xlink:href атрибута, а также устанавливаем x и y. Вы можете применять стили к use, и эти стили каскадно применятся к содержимому use элемента.

Но откуда берется содержимое <use>? Где происходит клонирование? И как CSS работает с этим содержимым?

Прежде чем отвечать на эти вопросы, стоит упомянуть несколько статей для лучшего понимания этих элементов и атрибута viewBox, который использует <symbol>:

SVG <use> и теневой DOM

Когда мы ссылаемся на элемент с помощью <use>, код примерно такой:

<symbol id="my-icon" viewBox="0 0 30 30">
	<!-- icon content / shapes here -->
</symbol>

<use xlink:href="#my-icon" x="100" y="300" />

На экране отобразится иконка, которая определена внутри элемента <symbol>, но это скорее содержимое <use>, коротое является дубликатом содержимого <symbol>.

Но элемент <use> одиночный - внутри него нет никакого содержимого, но где тогда происходит дублирование содержимого <symbol>?

Ответ - теневой DOM.

Что такое теневой DOM?

Теневой DOM подобен обычному DOM за исключением того, что узлы (nodes) в теневом DOM принадлежат не основному дереву документа, а конкретному его фрагменту. Это дает программистам возможность инкапсуляции, задания скриптов и стилей при создании модульных компонентов. Если вы когда-то использовали HTML5 элемент <video> и задумывались, где спрятан код его элементов управления - значит вы уже сталкивались с теневым DOM.

В примере с <use>, контент связанного элемента дублируется во фрагмент документа, который располагается в <use>. В данном случае <use> называется теневым хостом (Shadow Host).

Итак, содержимое <use> (клон или копия любого ссылающегося элемента) присутствует в теневом фрагменте документа.

Другими словами, содержимое там есть, но его не видно. Оно такое же как и содержимое обычного DOM, но вместо того, чтобы быть доступно на высоком уровне ("high-level" DOM), оно копируется в определенный фрагмент документа в <use>

Возможно сейчас вы задумались, можно ли увидеть эти скрытые части документа? Да, это возможно с помощью инструментов разработчика Chrome. (В Firefox пока нельзя). Итак, для того чтобы включить просмотр теневого DOM, нужно в основных настройках (вкладка General) активировать соответствующий флаг (Show user agent shadow DOM). Настройки открываются при клике по значку шестеренки. Подробнее об этом можно почитать здесь.

После того, как мы включили показ теневого DOM, на панели Elements можно увидеть дублированные элементы в виде обычных DOM элементов. На следующем изображении показан пример <use> элемента, ссылающегося на содержимое <symbol>. Обратите внимание на #shadow-root и его содержимое.

 

С помошью инструментов разработчика Chrome, вы можете проинспектировать содержимое элемента <use> внутри теневого DOM.

Глядя на этот код, вы можете убедиться, что теневой DOM очень похож на обычный DOM, за исключением разного поведения при обработке CSS и JavaScript из главного документа. Есть еще и другие отличия, но здесь о них говорить не будем, т.к. тема слишком обширная. Если хотите узнать больше об этом, рекомендую следующие статьи:

Лично я, учитывая насколько ограничены мои взаимодействия с теневым DOM, думаю о нем как об обычном DOM кроме случаев обработки стилей через CSS (и JavaScript). Вся суть элемента <use> заключается в возможности создания различных "копий" элемента, но во многих случаях нам нужно стилизовать каждую копию по-разному. Например представьте логотип в двух стилях (инвертированные цветовые темы), или разноцветные иконки, каждая из которых имеет свою цветовую тему. И конечно мы ожидаем, что сможем сделать это с помощью CSS.

Но как мы уже говорили ранее, содержимое теневого DOM нельзя стилизовать так же, как для обычного. Но тогда как нам быть? Мы не можем написать что-то вроде:

use path#line {
    stroke: #009966;
}

потому что не имеем доступа обычных CSS селекторов к теневому DOM.

Есть несколько специальных селекторов, которые позволяют "проникнуть" в теневой DOM, чтобы применить стили к узлам внутри него, но эти селекторы не только имеют плохую поддержку, но и очень ограничены по сравнению с обычными CSS селекторами. Кроме того, нам хочется более простого способа для стилизации содержимого <use> без необходимости "пачкать руки" в специфических особенностях теневого DOM. Мы просто хотим использовать CSS и SVG. 

Чтобы этого добиться и получить немного больше контроля над процессом стилизации, мы должны думать под другим углом. И начнем с возможностей наследования в CSS.

Каскадные стили

В SVG элементы могут быть стилизованы тремя способами: внешние CSS стили (внешний файл), внутренние стили (элементы <style>) и встроенные стили (в виде атрибута style). Для нас имеет смысл то, как происходит применение стилей к элементам.

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

В следующем куске кода, который просто отображает розовый круг с желтой обводкой, stroke, stroke-width и fill являются атрибутами предстваления.

<svg viewBox="0 0 100 100">
    <circle fill="deepPink" stroke="yellow" stroke-width="5" cx="50" cy="50" r="10"></circle>
</svg>

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

Спецификация SVG перечисляет, какие SVG атрибуты могут быть установлены через CSS свойства. Некоторые из этих атрибутов доступны как CSS свойства (например opacity и transform), а некоторые - нет (fill, stroke, stroke-width).

В SVG 2 этот список пополнился x, y, width, height, cx, cy и несколькими другими атрибутами представления, которые невозможно было установить через CSS в SVG 1.1. Новый список атрибутов в спецификации SVG 2.

Если вы похожи на меня, то ожидаете, что у атрибутов представления больший приоритет, чем у других объявлений стилей. Я подразумеваю, что внешние стили переопределяются внутренними, внутренние - встроенными (inline). Т.о. наиболее "внутренние" стили имеют больший приоритет, и когда свойство указывается в атрибуте, оно более значимое и переопределяет все остальные объявления. Однако это не так.

На самом деле атрибуты представления имеют низкий приоритет, ниже внешних, внутренних и встроенных стилей. Они могут переопределить наследуемые элементом стили, и сами переопределяются любыми другими объявлениями стилей.

Отлично, теперь когда с этим все прояснилось, давайте вернемся к элементу <use> и его содержимому.

Теперь мы в курсе, что не можем задать стили внутри элемента <use> при помощи CSS селекторов. Также мы знаем, что стили, примененные к элементу <use>, будут унаследованы всеми его потомками (которые внутри теневого DOM).

Итак, первой попыткой изменить цвет заливки элемента внутри <use> будет применение этого стиля непосредственно на <use> и пусть наследование делает свое дело.

Однако это вызовет две проблемы:

  1. Цвет заливки будет наследоваться всеми потомками <use>, даже теми, которые не нужно стилиизовать. (Если внутри <use> только один элемент, то это не проблема)
  2. Если вы экспортируете SVG из графического редактора или получили его от дизайнера, и по какой-то причине не можете отредактировать SVG код, вероятно вы остановитесь на атрибутах представления, которые в свою очередь переопределят любые стили, применяемые к <use>. И если вы определяете стили на <use>, вы хотите, чтобы они были унаследованы всеми потомками, т.о. атрибуты представления вызвали бы неудобство.

И даже если у вас есть доступ к коду SVG и вы можете избавиться от атрибутов представления, настоятельно рекомендую этого не делать, потому что:

  1. Удаление атрибутов, используемых для установки определенных свойств, сбросит эти свойства в значения браузера по умолчанию - в большинстве случаев это черная заливка и обводка (если говорить о цвете)
  2. Сбрасывая все значения, вы вынуждаете себя определять стили для всего набора свойств.
  3. Первоначально доступные атрибуты представления являются отличным механизмом запасного варианта, когда устанавливаемые вами внешние стили по какой-либо причине не применились. Если CSS файл не сможет подгрузиться, у вашей иконки по крайней мере будут хорошие стили по умолчанию. Я настоятельно рекомендую их сохранять.

Хорошо, теперь у нас есть эти атрибуты, но предположим, что мы хотим стилизовать каждый экземпляр иконки по-разному.

Давайте начнем с простого примера, а затем перейдем к более сложным.

Переопределение атрибутов представления через CSS

Атрибуты представления переопределяются другими объявленными стилями. Мы можем этим воспользоваться, чтобы внешние стили переопределяли свойства наследуемых значений элемента <use>.

Используя ключевое слово inherit в CSS, можно сделать это довольно просто. Посмотрите на следующий пример, в котором имеется иконка мороженого, сделанная с помощью одной линии. Автор иконки Erin Agnolu из проекта Noun. Пусть мы хотим изменить цвет единственной линии для разных экземпляров.

<svg>
  <symbol id="ic">
    <path fill="#000" d="M81,40.933c0-4.25-3-7.811-6.996-8.673c-0.922-5.312-3.588-10.178-7.623-13.844  c-2.459-2.239-5.326-3.913-8.408-4.981c-0.797-3.676-4.066-6.437-7.979-6.437c-3.908,0-7.184,2.764-7.979,6.442  c-3.078,1.065-5.939,2.741-8.396,4.977c-4.035,3.666-6.701,8.531-7.623,13.844C22.002,33.123,19,36.682,19,40.933  c0,2.617,1.145,4.965,2.957,6.589c0.047,0.195,0.119,0.389,0.225,0.568l26.004,43.873c0.383,0.646,1.072,1.04,1.824,1.04  c0.748,0,1.439-0.395,1.824-1.04L77.82,48.089c0.105-0.179,0.178-0.373,0.225-0.568C79.855,45.897,81,43.549,81,40.933z   M49.994,11.235c2.164,0,3.928,1.762,3.928,3.93c0,2.165-1.764,3.929-3.928,3.929s-3.928-1.764-3.928-3.929  C46.066,12.997,47.83,11.235,49.994,11.235z M27.842,36.301c0.014,0,0.027,0,0.031,0c1.086,0,1.998-0.817,2.115-1.907  c0.762-7.592,5.641-13.791,12.303-16.535c1.119,3.184,4.146,5.475,7.703,5.475c3.561,0,6.588-2.293,7.707-5.48  c6.664,2.742,11.547,8.944,12.312,16.54c0.115,1.092,1.037,1.929,2.143,1.907c2.541,0.013,4.604,2.087,4.604,4.631  c0,1.684-0.914,3.148-2.266,3.958H25.508c-1.354-0.809-2.268-2.273-2.268-3.958C23.24,38.389,25.303,36.316,27.842,36.301z   M50.01,86.723L27.73,49.13h44.541L50.01,86.723z"/>
  </symbol>
</svg>

Содержание иконки (path) определяется внутри элемента <symbol>. Это означает, что они не будут отображаться на холсте.

Затем мы рендерим несколько экземпляров иконки с помощью <use>.

<svg class="icon" viewBox="0 0 100 125"> 
    <use class="ic-1" xlink:href="#ic" x="0" y="0" />
</svg>
<svg class="icon" viewBox="0 0 100 125"> 
    <use class="ic-2" xlink:href="#ic" x="0" y="0" />
</svg>

И устанавливаем ширину и высоту через CSS. Я использую такие же размеры как у viewBox, но они не обязательно должны быть такими же. Однако, чтобы избежать избытка пустого места внутри SVG, убедитесь, что пропорции сохранены.

.icon {
    width: 100px;
    height: 125px;
}

В итоге получается следующее:

Обратите внимание на черные границы, т.о. можно видеть границы каждого SVG и удостовериться, что в первом случае (где мы лишь определили иконку) ничего не рандерится. Итак, SVG документ все равно будет отображаться на странице, даже если в нем нечего показывать. Чтобы этого избежать, скрываем первый SVG (display: none). Если этот элемент не скрывать, по умолчанию он будет показываться на странице с размерами 300x150px, так что в итоге вы получите белую область на странице.

Теперь попробуем изменить цвет заливки для разных экземпляров:

use.ic-1 {
    fill: skyblue;
}
use.ic-2 {
    fill: #FDC646;
}

Цвет все еще не изменился, т.к. он наследуется от элемента path (fill="#000"). Поэтому для path наследуем цвет заливки:

svg path {
    fill: inherit;
}

И вуаля! Теперь цвет, указанный для каждого <use>, применяется для path. Посмотрите демо, поиграйтесь с цветами, создайте больше экземпляров:

Эта техника полезна, когда нужно применить внутри <use> наследуемые от главного элемента стили. Но в большинстве случаев требуется не совсем то. Поэтому перейдем к другим примерам.

Стилизация соедержимого <use> с помощью CSS свойства all

Некоторое время назад я работала над иконкой, на которую ссылалась через <use>, и хотела, чтобы один из элементов внутри наследовал все стили (fill, stroke, stroke-fill, opacity и даже transform) от <use>. В общем я хотела управлять всеми этими атрибуатми из CSS, при этом оставляя атрибуты представления в разметке как запасной вариант.

Оказавшись в такой же ситуации, вы вероятно напишете в CSS:

path#myPath {
    fill: inherit;
    stroke: inherit;
    stroke-width: inherit;
    transform: inherit;
    /* ... */
}

Видно, что нужно устанавливать все свойства в inherit. К счастью, нас выручит CSS свойство all. Я уже писала о нем в заметке, но стоит еще раз упомянуть в данном контексте.

Используя свойство all, мы можем переписать код следующим образом:

path#myPath {
    all: inherit;
}

Это отлично работает во всех браузерах, которые поддерживают данное свойство (посмотрите упомянутую заметку), однако есть важное замечание: это объявление установит буквально все свойства в значение inherit, даже те, которые вам не хочется. Поэтому применять all - это лишь чрезвычайная мера, особенно полезная, если вы хотите "заложить основу" элемента и можете управлять свойствами через CSS. Если вы будете использовать такое объявление, не определив конкретные значения для всех свойств в CSS, они каскадно будут искать родительские значение, и в большинстве случаев применят стили браузера по умолчанию.

Обратите внимание, что это затронет атрибуты, которые могут быть установлены через CSS, не SVG атрибуты. Т.о. если атрибут может быть установлен через CSS, он будет искать значение родителя, а иначе - не будет.

Мы в состоянии заставить атрибуты представления наследоваться от стилей элемента <use>. Но что если наша иконка содержит несколько элементов, и мы не хотим, чтобы все элементы наследовали свойства от <use>? Что делать, если мы хотим применить разные цвета для разных элементов внутри <use>? Установка одного стиля к use больше не поможет, нужно что-то еще, чтобы помочь нам задать нужные цвета нужным элементам...

Использование CSS-переменной currentColor для стилизации содержимого <use>

Использовуя CSS-переменную currentColor в сочетании с описанной техникой, можно указать два разных цвета на элементе вместо одного. Fabrice Weinberg описал этот способ в своем Codepen блоге около года назад.

Идея заключается в том, чтобы применить к <use> оба свойства (fill и color). Давайте сразу перейдем к примеру кода, чтобы увидеть как это работает.

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

Для начала сделаем разметку для этого примера: должен быть <symbol>, содержащий определение нашей иконки и 3 экземпляра <use>.

<svg style="display: none;">
<symbol id="codrops" viewBox="0 0 23 30">
    <path class="back" fill="#aaa" d="M22.63,18.261c-0.398-3.044-2.608-6.61-4.072-9.359c-1.74-3.271-3.492-5.994-5.089-8.62l0,0   c-1.599,2.623-3.75,6.117-5.487,9.385c0.391,0.718,0.495,1.011,0.889,1.816c0.143,0.294,0.535,1.111,0.696,1.43   c0.062-0.114,0.582-1.052,0.643-1.162c0.278-0.506,0.54-0.981,0.791-1.451c0.823-1.547,1.649-2.971,2.469-4.33   c0.817,1.359,1.646,2.783,2.468,4.33c0.249,0.47,0.513,0.946,0.791,1.453c1.203,2.187,2.698,4.906,2.96,6.895   c0.292,2.237-0.259,4.312-1.556,5.839c-1.171,1.376-2.824,2.179-4.663,2.263c-1.841-0.084-3.493-0.887-4.665-2.263   c-0.16-0.192-0.311-0.391-0.448-0.599c-0.543,0.221-1.127,0.346-1.735,0.365c-0.56-0.019-1.095-0.127-1.599-0.313   c1.448,3.406,4.667,5.66,8.447,5.78C19.086,29.537,23.469,24.645,22.63,18.261z"/>
    <path class="front" fill="#ddd" d="M6.177,11.659c0.212,0.367,0.424,0.747,0.635,1.136c0.164,0.303,0.333,0.606,0.512,0.927   c0.683,1.225,1.618,2.898,1.755,3.937c0.144,1.073-0.111,2.056-0.716,2.769c-0.543,0.641-1.315,1.014-2.186,1.067   c-0.87-0.054-1.643-0.43-2.186-1.067c-0.604-0.713-0.858-1.695-0.715-2.771c0.137-1.036,1.072-2.712,1.755-3.936   c0.18-0.32,0.349-0.623,0.513-0.927C5.752,12.404,5.964,12.026,6.177,11.659 M6.177,5.966L6.177,5.966   c-1.02,1.649-2.138,3.363-3.247,5.419c-0.932,1.728-2.344,3.967-2.598,5.88c-0.535,4.014,2.261,7.09,5.846,7.203   c3.583-0.113,6.379-3.189,5.845-7.203c-0.255-1.912-1.666-4.152-2.598-5.88C8.314,9.329,7.196,7.617,6.177,5.966L6.177,5.966z"/>
</symbol>
</svg>
<svg height="90px" width="69px">
  <use xlink:href="#codrops" class="codrops-1"/>
</svg>
<svg height="90px" width="69px">
  <use xlink:href="#codrops" class="codrops-2"/>
</svg>
<svg height="90px" width="69px">
  <use xlink:href="#codrops" class="codrops-3"/>
</svg>

Если мы установим fill на элемент <use>, этот цвет будет применяться к обоим path, и получатся две одноцветные капельки - это не то, чего мы хотим.

Здесь мы будем использовать currentColor для разделения цветов. Вставляем currentColor в том месте, где хотим применить другой цвет. Это должно быть в определении нашей иконки, т.е. внутри элемента <symbol>. Получаем следующий код:

<svg style="display: none;">
	<symbol id="codrops" viewBox="0 0 23 30">
		<path class="back" fill="#aaa" d="..."/>
		<path class="front" fill="currentColor" d="..."/>
	</symbol>
</svg>

Далее мы должны удалить атрибут представления fill для второй капельки, позволив применить цвет заливки use, без использования техники наследования.

Если мы будем использовать ключевое слово inherit для того, чтобы атрибуты представления унаследовали свои значения от <use>, тогда оба path будут иметь одинаковые значения цвета, и мы не увидим никакого эффекта от использования currentColor. В нашем случае нужно удалить атрибут, который хотим устанавливать через CSS, и оставить только один path со значением fill="currentColor".

<svg style="display: none;">
	<symbol id="codrops" viewBox="0 0 23 30">
		<path class="back" d="..."/>
		<path class="front" fill="currentColor" d="..."/>
	</symbol>
</svg>

Теперь, используя свойства fill и color, мы можем стилизовать капельки:

.codrops-1 {
  fill: #4BC0A5;
  color: #A4DFD1;
}
.codrops-2 {
  fill: #0099CC;
  color: #7FCBE5;
}
.codrops-3 {
  fill: #5F5EC0;
  color: #AEAFDD;
}

Каждый элемент <use> получает свои значения fill и color. Для каждого цвет fill наследуется от первого path, который не имеет атрибута fill, и значение свойства color используется в качестве атрибута fill для второго path.

Вот результат:

Эта двухцветная техника весьма полезна для простых двухцветных логотипов. В своей статье Fabrice делает три разных варианта логотипа Sass, изменяя цвет текста и цвет фона.

На сегодня currentColor - это единственная доступная переменная в CSS. Однако, если бы у нас было больше переменных, мы бы смогли передать больше значений в содержимое <use>. Amelia Bellamy-Royds представляла эту концепцию в записи своего Codepen блога около года назад. Давайте посмотрим, как это работает.

Будущее: стилизация содержимого <use> с помощью кастомных CSS свойств (CSS переменных)

Используя собственные CSS свойства (CSS переменные), вы можете стилизовать содержимое <use> без необходимости переопределения значений атрибутов представления.

Как сказано в MDN, CSS переменные - это объекты, определяемые авторами или пользователями на веб страницах для того, чтобы хранить определенные значения всюду в документе. Они устанавливаются, используя кастомные свойства и для получаения доступа используется var(). Они очень похожи на переменные CSS препроцессоров (типа Sass), но более гибкие и могут делать вещи, которые переменные препроцессора не умеют.

У переменных, будь то CSS переменные или переменные препроцессора, есть много примеров использования, но именно изменение цветов является наиболее распространенным примером. И здесь мы рассмотрим, как это может быть применено для SVG.

Мы рассмотрим одно изображение, определенное в <symbol> и один элемент <use>, однако все описанное может быть применено и для множества элементов <use>.

Предположим, у нас есть иллюстрация милого робота-хипстера (от Freepik).

Код разметки для этого робота такой:

<svg style="display: none">
    <symbol id="robot" viewBox="0 0 340 536">
        <path d="..." fill="#fff" />
        <path d="..." fill="#D1312C" />
        <path d="..." fill="#1E8F90" />
        <path d="..." fill="#1E8F90" />
        <path d="..." fill="#fff" />
        <path d="..." fill="#6A4933" />
        <path d="..." fill="#1E8F90"  />
        <path d="..." fill="#6A4933" />
        <path d="..." fill="#fff" />
        <path d="..." fill="#6A4933" />
        <path d="..." fill="#F2B42B" />
        <path d="..." fill="#fff" />
     
       <!-- rest of the shapes -->
    </symbol>
</svg>

Мы не собираемся использовать CSS переменные как значения fill для каждого path; вместо этого, мы собираемся задать значение fill через CSS, и при этом храним атрибуты fill в разметке. Эти атрибуты будут использованы в качестве запасного варианта для браузеров, не поддерживающих CSS переменные. Поэтому в таких браузерах изображение будет показываться с первоначальными цветами.

С добавлением переменных, получается такой код:

<svg style="display: none">
    <symbol id="robot" viewBox="0 0 340 536">
        <path d="..." fill="#fff" />
        <path d="..." fill="#D1312C" />
        <path d="..." fill="#1E8F90" style="fill: var(--primary-color)" />
        <path d="..." fill="#1E8F90" style="fill: var(--primary-color)" />
        <path d="..." fill="#fff" />
        <path d="..." fill="#6A4933" style="fill: var(--tertiary-color)" />
        <path d="..." fill="#1E8F90" style="fill: var(--primary-color)" />
        <path d="..." fill="#6A4933" style="fill: var(--tertiary-color)" />
        <path d="..." fill="#fff" />
        <path d="..." fill="#6A4933" style="fill: var(--tertiary-color)" />
        <path d="..." fill="#F2B42B" style="fill: var(--secondary-color)" />
        <path d="..." fill="#fff" />
       
        <!-- rest of the shapes -->
    </symbol>
</svg>

Т.к. встроенные стили переопределяют атрибуты представления, браузеры, поддерживающие CSS переменные, будут использовать эти переменные в качестве цвета заливки. А браузеры, не имеющие поддержки CSS переменных, будут вместо этого использовать значение атрибута fill.

Далее мы должны определить значения переменных в CSS. Для начала покажем изобрачение с помощью <use>:

<svg width="340" height="536">
    <use xlink:href="#robot" id="robot-1" />
</svg>

Затем переменные должны быть определены на элементе <use>, чтобы они каскадом перешли в его содержимое. Цвета, которые мы выберем для переменных, составят цветовую схему нашей иллюстрации. Для робота имелось три основных цвета: которые я назову primary, secondary и tertiary.

#robot-1 {
  --primary-color: #0099CC;
  --secondary-color: #FFDF34;
  --tertiary-color: #333;
}

После применения вышеупомянутых цветов для переменных, получается немного другой робот:

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

Мы упомянули, что браузеры, не поддерживающие CSS переменные, в качестве запасного варианта используют стили из атрибутов представления; браузеры, имеющие поддержку, будут использовать именно переменные вместо атрибутов. Отлично. Но что произойдет, если браузер поддерживает CSS переменные, но автор забывает указать значение переменной, или указано неверное значение?

Для нашего робота мы определили 3 переменные, и несколько элементов осталось без определения. Если мы запустим текущий код в браузере, который поддерживает CSS переменные (в настоящее время только Firefox), и удалите объявление переменной из CSS, вот что получится:

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

Способом избежать этого является указание других "запасных" цветов для браузеров. Синтаксис CSS переменных подразумевает использование "запасных" цветов. Они указываются в var() вторым аргументом. Т.о. код изменится:

<svg style="display: none">
    <symbol id="robot" viewBox="0 0 340 536">
        <path d="..." fill="#fff" />
        <path d="..." fill="#D1312C" />
        <path d="..." fill="#1E8F90" style="fill: var(--primary-color, #1E8F90)" />
        <path d="..." fill="#1E8F90" style="fill: var(--primary-color, #1E8F90)" />
        <path d="..." fill="#fff" />
        <path d="..." fill="#6A4933" style="fill: var(--tertiary-color, #6A4933)" />
        <path d="..." fill="#1E8F90" style="fill: var(--primary-color, #1E8F90)" />
        <path d="..." fill="#6A4933" style="fill: var(--tertiary-color, #6A4933)" />
        <path d="..." fill="#fff" />
        <path d="..." fill="#6A4933" style="fill: var(--tertiary-color, #6A4933)" />
        <path d="..." fill="#F2B42B" style="fill: var(--secondary-color, #F2B42B)" />
        <path d="..." fill="#fff" />

         <!-- rest of the shapes -->
    </symbol>
</svg>

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

Используя эту технику, вы можете ссылаться на робота из любого места на странице с помощью элемента <use>, и для каждого нового экземпляра задавать цвета через CSS, тем самым создавая различные цветовые темы.

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

Если вы смотрите демо в Firefox, то должны видеть синюю + желтую версию робота, как определяется в CSS переменных. Если смотрите в Chrome - зеленую версию, вступает в игру запасной вариант. Попробуйте также удалить объявление CSS переменных и посмотреть, как работает запасной вариант в Firefox.

Заключение

Уф. Многовато получилось.

Используя преимущества CSS каскадирования, стилизация содержимого <use> (в теневом DOM) может стать проще. А с помощью CSS переменных (будь то единственный currentColor или кастомные переменные) мы можем проникнуть внутрь теневого DOM и кастомизировать графику так, как нам захочется, при этом обеспечивая хороший запасной вариант, когда что-то пойдет не так.

Лично я невероятно поражена связкой CSS переменные + SVG. Мне нравятся их возможности, при том что имеется отличный запасной механизм. Как уже говорилось ранее, пока поддержка реализована только в Firefox, но мы можем добиться более широкой поддержки браузерами, например проголосовав на форуме MS Edge.

В будущем мы даже можем получить другие способы стилизации содержимого <use>, уже есть дискуссии по поводу использования CSS переменных а качестве SVG параметров. Т.о. хотя эта статья оказалась длинной, все же не затронула все, что нужно знать по этой теме.

Инспектирование содержимого <use> элементов было одним из частых вопросов, вызывающих недопонимание. На эту тему существует много статей, но здесь не об этом.

Надеюсь статья вам понравилась и вы нашли что-то полезное для себя.

Спасибо за прочтение!

Оригинал статьи