Переменные в CSS: зачем они нам?

CSS-переменные, или, точнее, кастомные CSS-свойства, доступны в Chrome начиная с 49 версии. Они могут быть полезны для уменьшения количества повторяющегося кода в CSS, создания впечатляющих эффектов вроде смены тем на лету и, потенциально, создания полифилов для будущих возможностей CSS.

Хаос в CSS

Обычная практика при разработке приложения — составить набор фирменных цветов, которые будут использоваться, чтобы поддержать единый стиль. Увы, многократное использование этих цветов в CSS не только является рутинной работой, но ещё и создаёт пространство для возникновения ошибок. Если в какой-то момент один из цветов нужно будет поменять, разработчик может неосторожно воспользоваться поиском и заменой по всему документу, что в большом проекте может быть довольно опасно.

В последнее время многие разработчики стали использовать CSS-препроцессоры типа SASS или LESS, которые решают эту проблему с помощью переменных. Хотя эти инструменты заметно увеличили продуктивность разработки, препроцессорные переменные имеют очень серьёзный недостаток: они статичны и не могут меняться на лету. Появление возможности динамически менять переменные не только позволит на лету менять темы сайта или приложения, но также означает значительное расширение возможностей отзывчивого дизайна и возможность создания полифилов для будущих свойств CSS. С выходом Chrome 49 переменные стали доступны в виде кастомных CSS-свойств.

Кастомные свойства в двух словах

Кастомные свойства расширяют наш CSS-инструментарий двумя новыми возможностями:

Краткий пример для демонстрации:

:root {
  --main-color: #06c;
}
 
#foo h1 {
  color: var(--main-color);
}

--main-color — это определённое автором кастомное свойство со значением #06c. Заметьте, что все кастомные свойства начинаются с двух дефисов.

Функция var() возвращает значение кастомного свойства и заменяется им, в результате чего получается color: #06c;. Если кастомное свойство где-то определено в таблице стилей, оно будет доступно функции var.

На первый взгляд синтаксис может показаться странным. Многие разработчики недоумевают: «Почему бы просто не использовать $foo в качестве имён переменных?» Это было сделано специально для повышения гибкости и возможности в перспективе создавать макросы для $foo. Более подробно об этом можно прочесть в статье одного из авторов спецификации, Таба Аткинса (Tab Atkins).

Синтаксис кастомных свойств

Синтаксис кастомных свойств довольно прост:

--header-color: #06c;

Обратите внимание, что кастомные свойства регистрозависимы, то есть --header-color и --Header-Color — это два разных свойства. Хотя синтаксис поначалу может показаться незамысловатым, на самом деле он позволяет сделать довольно много. К примеру, ниже — пример валидного кастомного свойства:

--foo: if(x > 5) this.width = 10;

Хотя это выражение не будет работать в качестве переменной (а также не будет валидным значением любого обычного свойства), потенциально оно может быть прочитано и обработано на лету с помощью JavaScript. Это означает, что кастомные свойства могут открыть доступ ко всевозможным интересным техникам, недоступным с нынешними CSS-препроцессорами. Так что если вы, позёвывая, думаете что-то вроде «Какая разница, у меня есть SASS…», подумайте ещё раз! Это не те переменные, с которыми вы привыкли работать.

Каскад

Кастомные свойства следуют стандартным правилам каскада, так что вы можете определить одно и то же свойство на разных уровнях специфичности:

:root { --color: blue; }
div { --color: green; }
#alert { --color: red; }
* { color: var(--color); }
 
<p>У меня синий цвет, унаследованный от root!</p>
<div>А для меня установлен зелёный!</div>
<div id="alert">
  Ну а для меня - красный!
  <p>И для меня красный: из-за наследования!</p>
</div>

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

:root {
  --gutter: 4px;
}
 
section {
  margin: var(--gutter);
}
 
@media (min-width: 600px) {
  :root {
    --gutter: 16px;
  }
}

Необходимо отметить, что вышеприведённый приём невозможно повторить используя CSS-препроцессоры, потому что они неспособны переопределять переменные внутри медиавыражений. У этой возможности огромный потенциал!

Также есть возможность использовать кастомные свойства, получающие своё значение из других кастомных свойств. Это может быть крайне полезно для настройки тем:

:root {
  --primary-color: red;
  --logo-text: var(--primary-color);
}

Функция var()

Чтобы получить и использовать значение кастомного свойства, понадобится функция var(). Вот её синтаксис:

var(<custom-property-name> [, <declaration-value> ]? )

Здесь <custom-property-name> — имя определённого автором кастомного свойства, <declaration-value> — фолбек, который будет использован, если упомянутое кастомное свойство не является валидным. Фолбек может быть списком, разделённым запятыми, он будет преобразован к единому значению. Например, var(--font-stack, "Roboto", "Helvetica"); определяет фолбек "Roboto", "Helvetica". Обратите внимание, что краткая запись некоторых свойств (как в случае внешних и внутренних отступов) разделяется не запятыми, а пробелами, так что валидный фолбек для внутренних отступов будет выглядеть примерно так:

p {
  padding: var(--pad, 10px 15px 20px);
}

С такими фолбеками автор компоненты может написать для своего элемента пуленепробиваемые стили:

/* В стилях компоненты: */
.component .header {
  color: var(--header-color, blue);
}
.component .text {
  color: var(--text-color, black);
}
 
/* В стилях основного приложения: */
.component {
  --text-color: #080;
    /* header-color не установлен,
       поэтому остаётся синим
       в соответствии с фолбеком */
}

Этот метод особенно пригодится для стилизации веб-компонент, использующих Shadow DOM, поскольку кастомные свойства могут пересекать теневые границы. Автор веб-компоненты может создать начальный дизайн при помощи фолбеков, а потом настроить стили при помощи кастомных свойств:

<!-- В определении веб-компоненты: -->
<x-foo>
  #shadow
    <style>
      p {
        background-color: var(--text-background, blue);
      }
    </style>
    <p>
      У этого текста жёлтый фон, потому что так указано в документе!
      Иначе был бы синий.
    </p>
</x-foo>
 
/* В стилях основного приложения: */
x-foo {
  --text-background: yellow;
}

При использовании var() нужно иметь в виду несколько нюансов. Например, переменные не могут быть именами свойств:

.foo {
  --side: margin-top;
  var(--side): 20px;
}

Это не является эквивалентом присваивания margin-top: 20px;. Более того, второе объявление не является валидным, и выбросит ошибку.

Аналогично, невозможно создать значение, часть которого берётся из переменной:

.foo {
  --gap: 20;
  margin-top: var(--gap)px;
}

Это тоже не является эквивалентом margin-top: 20px;. Чтобы собрать значение, понадобится кое-что другое: функция calc().

Создание значений с помощью calc()

На случай, если вы никогда раньше с ней не работали, функция calc() — это небольшой, удобный инструмент, позволяющий выполнять вычисления, чтобы определять значения в CSS. Она поддерживается всеми современными браузерами, и её можно использовать вместе с кастомными свойствами для создания новых значений. Например:

.foo {
  --gap: 20;
  margin-top: calc(var(--gap) * 1px); /* зашибись */
}

Работа с кастомными свойствами в JavaScript

Чтобы получить значение кастомного свойства, используйте метод getPropertyValue() объекта CSSStyleDeclaration.

/* CSS */
:root {
  --primary-color: red;
}
 
p {
  color: var(--primary-color);
}
 
<!-- HTML -->
<p>Этот абзац красного цвета!</p>
 
/* JS */
var styles = getComputedStyle(document.documentElement);
var value = String(styles.getPropertyValue('--primary-color')).trim();
// value = 'red'

Аналогично, чтобы динамически менять значение кастомного свойства, используйте метод setProperty() объекта CSSStyleDeclaration.

/* CSS */
:root {
  --primary-color: red;
}
    
p {
  color: var(--primary-color);
}
 
<!-- HTML -->
<p>А теперь этот абзац зелёного цвета!</p>
 
/* JS */
document.documentElement.style.setProperty('--primary-color', 'green');

Также при задании значения кастомного свойства на лету можно использовать ссылку на другое кастомное свойство, вставив функцию var() в вызов setProperty().

/* CSS */
:root {
  --primary-color: red;
  --secondary-color: blue;
}
 
<!-- HTML -->
<p>Здорово! Этот абзац синего цвета!</p>
 
/* JS */
document.documentElement.style.setProperty('--primary-color', 'var(--secondary-color)');

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

Поддержка браузерами

На данный момент кастомные свойства поддерживаются в Chrome 49, Firefox 42, Safari 9.1, и iOS Safari 9.3.

Демо

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

Материалы для дальнейшего изучения

Если вы хотите узнать больше про кастомные свойства, Филип Уолтон (Philip Walton) из команды Google Analytics написал учебник для начинающих про то, почему он в восторге от кастомных свойств, а также за их появлением в других браузерах можно следить на chromestatus.com.

Если не указано обратного, всё содержимое этой страницы находится под лицензией Creative Commons Attribution 3.0 License, а фрагменты кода находятся под лицензией Apache 2.0 License. Подробнее см. Terms of Service.