Что умеют и чего не умеют CSS Custom Properties.

Liudmila Mzhachikh
5 min readMay 2, 2018

--

Если вы используете в своем проекте препроцессоры (SASS, LESS или Stylus), то точно знаете, какие преимущества дает их использование: переменные, операторы, функции и миксины и т. д. Но с появлением CSS-переменных (CSS Custom Properties — пользовательские свойства CSS) в них не просто отпала необходимость, а ушли в прошлое некоторые ограничения препроцессорных переменных, которых нет у CSS-переменных. Я рассмотрю некоторые юзкейсы и постараюсь доказать, что пришло время переходить на CSS Custom Properties, если вы этого еще не сделали.

Объявление и использование

Переменная объявляется так: двойной дефис + имя переменной. Получение значения с помощью ключевого слова var:

.my-block {
--color-red: red;
color: var(--color-red);
}

Глобальная переменная:

:root {
--color-red: red;
}
.my-block {
color: var(--color-red);
}

Можно задавать дефолтное значение, оно применится, если переменная недоступна:

body {
color: var(--color-red, blue);
}

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

body {
--color-red: red;
}
.paragraph {
color: var(--color-red);
}

И даже так:

body {
--default-text: 'Hello,';
}
.paragraph::after {
content: var(--default-text)' Lucy'
}

Результатом будет конкатенация строк.

Помогают избегать дублирования

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

:root {
--color-primary: blue;
--color-secondary: lightblue;
}
.paragraph {
color: var(--color-primary);
}
.additional-text {
color: var(--color-secondary);
}

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

А еще можно хранить в переменной значения “длинных” CSS-свойств, чтобы дублировать все значение, если надо изменить только его часть. Вместо

.paragraph {
transform: rotate(90deg) translate(10px, 10px) skew(30deg);
}
.paragraph:hover {
transform: rotate(90deg) translate(10px, 10px) skew(45deg);
}

Получим:

.paragraph {
transform: rotate(90deg) translate(10px, 10px) skew(var(--skew-value, 30deg));
}
.paragraph:hover {
--skew-value: 45deg;
}

Одну переменную можно использовать для различных свойств. Чтобы браузер понял, что делать со значением, можно применить calc:

:root {
--big-screen: 0;
}
@media (min-width: 500px) {
.paragraph {
--big-screen: 1;
}
}
.paragraph {
font-size: calc(
var(--big-screen) * 20px + (1 - var(--big-screen)) * 13px
);
}

В этом примере размер шрифта будет 13px на маленьких экранах и 20px на больших.

Эмуляция несуществующего свойства

Вот очень интересный юзкейс. Выше я уже привела пример с использованием переменной для части составного значения свойства. Например, мы хотим проэмулировать свойство box-shadow-color, которого в CSS нет:

Использование в SVG

CSS-переменные можно прокинуть в SVG

<svg>
<path fill="var(--my-color)">
</svg>

Доступ из JS

Зачем доступ к CSS из JS? Могут быть самые различные юзкейсы, например, вам нужно что-то подстветить в зависимости от ответа бэкенда. Получаем значение:

//CSS
body {
--links-color: orange;
}
//JS
const elem =
document.querySelector('.paragraph');
const propValue = getComputedStyle(elem)
.getPropertyValue('--links-color');

Устанавливаем значение:

elem.style
.setProperty('--links-color', 'red');

Знают о структуре DOM

Препроцессорные переменные ничего не знают о DOM-дереве. Такой пример работать не будет:

<div class="paragraph big">Some text</div>//Stylus
.paragraph {
$size = 15px;
font-size: $size;
}
.big {
$size = 30px;
}

А в случае с CSS-переменными — применится 30px;

.paragraph {
--size: 15px;
font-size: var(--size);
}
.big {
--size: 30px;
}

Препроцессорная переменная не применится, пока ее не присвоить:

.big {
$size = 30px;
font-size: $size;
}

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

Поддержка

Ну вы поняли) Впрочем, проблемы с поддержкой решаются при помощи cssnext (достаточно костыльно и с потерей многих фишек, присущих CSS-переменным, но в целом проблему поддержки решают. Так что решать вам).

Проблема еще в том, что любое выражение, содержащее CSS-переменную считается валидным. Так что применить фолбэк вот таким образом не получится, такое значение будет валидным и применится:

.paragraph {
font-size: 15px;
font-size: I LOVE CSS VAR(--I)ABLES;
}

Поддерживаются ли CSS-переменные можно с помощью директивы supports:

@supports (--css: variables) {
/* supported */
}
@supports (not(--css: variables)) {
/* not supported */
}

В этом случае вы можете написать 2 блока с кодом и, когда придет время, удалить ненужный — подход, вполне соотвествующий graceful degradation и progressive enhancement.

Не типизированы

CSS-переменные работают со строками. На самом деле вы можете запихнуть в переменную все, что угодно. Вот это тоже будет валидным значением:

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

“Подумаешь, проблема” — скажете вы. Нет типизации — нет проблем. Но отсутствие типизации также означает, что не будет подсказок в панели devtools, нельзя будет произвести статический анализ и многое другое. А еще рассмотрим следующий пример.

Не анимируются

Допустим, вы делаете боковую панель с меню, которое плавно выезжает при клике на “бургер”.

Ширина 250px может встречаться в нескольких местах, так что вполне логично вытащить ее в переменную. И тогда вы пишите что-то вроде того:

.menu-panel {
transition: --width 2s ease;
--width: 0;
width: var(--width);
}
.menu-panel.menu-panel_opened {
--width: 250px;
}

Но, увы, это не будет работать. Как было написано выше, значения CSS-переменных — это строки. Как рассчитать промежуточное состояние между двумя строками? браузер понятия не имеет. Поэтому, согласно спецификации, он скачком переключает значение на 50% от временного интервала. Так что, CSS-переменные не типизированы, а как следствие не анимируемы. Но к этой теме я еще вернусь в одном из следующих постов.

Подводя итог, какие преимущества у CSS Custom Properties по сравнению с препроцессорными переменными:

  • Это встроенная возможность CSS, часть спецификации, а не дополнение
  • Не надо учить синтаксис препроцессора, вы продолжаете писать CSS
  • Не требуется никаких дополнительных настроек
  • Изменения сразу появляются в браузере, не требуется компиляция, которая занимает время
  • Можно использовать статический анализатор кода, линтер
  • Знают о DOM-структуре
  • Можно использовать в SVG
  • Доступ из JS

Какие ограничения (пока что) имеют CSS-переменные:

  • Не типизированы
  • Как следствие, не анимируемы
  • Поддержка

Материалы по теме

Подписывайтесь на блоги:

Телеграм: frontend_thoughts

Instagram: lucy_frontend

--

--

Liudmila Mzhachikh

Frontend developer at Mail.Ru Group 👩‍💻, leader of moscowcss community, conference speaker 🎤, write about IT, channel: t.me/frontend_thoughts