Что умеют и чего не умеют CSS Custom Properties.
Если вы используете в своем проекте препроцессоры (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-переменные:
- Не типизированы
- Как следствие, не анимируемы
- Поддержка
Материалы по теме
- Спецификация
- CSS Houdini — from CSS variables to JavaScript and back — Serg Hospodarets
- Доклад Виталия Зюзина на pitercss_meetup
- Отличная статья Романа Комарова