CSS Custom Properties

08.05.2017
Смотрите видео
на Youtube

Для начала я вас немного замотивирую.

Рассмотрим следующий пример. Допустим у нас есть кликабельный блок с текстом.

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

Красиво было бы сделать так:


$color-title: #000000;

.container {
    &:hover {
        $color-title: #5cab22;
    }
}

.title {
    color: $color-title;
}

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

Но, в Sass, как впрочем и в другом препроцессоре, это работать не будет. После компиляции в переменную подставится черный цвет, а блок :hover отбросится совсем.

Еще пример. Опять у нас тот же блок, но на этот раз мы хотим, чтобы на экранах шириной больше 1000px отступ был немного больше.

И опять же, по-хорошему стоило бы сделать что-то такое:


$padding: 20px;

.container {
   padding: $padding;
}

@media (min-width: 1000px) {
    $padding: 50px;
}

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

И последний пример. Допустим у нас в этом же блоке будет svg-иконка и она должна быть в основном цвете сайта. Было бы здорово раскрасить ее как-то так, ну, вдруг основной цвет сайта в будущем изменится:


<svg>
   <path fill="$color-main" d="M42.5,0.003C19.028,..."/>
</svg>

Но, как вы могли догадаться, и этот код работать не будет, после компиляции переменные препроцессоров не существуют.

Вот мы рассмотрели три примера, с которыми препроцессорные переменные не справляются. Ну или справляются, но код при этом некрасиво разрастается. И так мы плавно подошли к пользовательским свойствам. Спойлер: пользовательские свойства с этими задачами справятся.

Кстати, официально спецификация CSS-переменных называется CSS Custom Properties for Cascading Variables — Пользовательские свойства CSS для каскадных переменных. Поэтому можно встретить термины CSS-переменные, пользовательские свойства, кастомные свойства — это все одно и то же.

Объявить кастомное свойство можно с помощью двойного дефиса:


:root {
    --color-main: #5cab22;
}

А получить значение с помощью функции var():


.box {
    color: var(--color-main, #000000);
}

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


.box {
    color: var(--color-main, var(--color-default));
}

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

Переменные можно использовать в функции calc():


:root {
    --base-font-size: 14px;

    --font-size-l: calc(var(--base-font-size) + 4px);
    --font-size-xl: calc(var(--base-font-size) + 10px);
}

Вы наверное обратили внимание, что в примерах переменные объявлены в псевдоклассе :root. Дело в том, что пользовательские свойства CSS, в отличии от препроцессорных переменных, не могут быть объявлены вне селектора, а :root выполняет роль глобального селектора — таким образом мы создаем глобальную переменную.

Пользовательские свойства подчиняются правилам каскада и наследуются до последнего потомка. Это позволяет переопределить значение переменной в родительском селекторе и все потомки будут использовать новое значение.

По поводу синтаксиса автор спецификации Таб Аткинс написал пост в своем блоге, где он объясняет, почему для кастомных свойств не использовали синтаксис с долларом из Sass. Он говорит, что синтаксис со знаком доллара ему нравится, но его решили оставить на тот случай, если в CSS появятся другие похожие на переменные вещи, для которых будет критичен короткий синтаксис.

А теперь вернемся к нашим примерам и перепишем их с использованием кастомных свойств.

Пример первый с изменением цвета заголовка при наведении:


:root {
   --color-title: #000000;
}

.container:hover {
   --color-title: #5cab22;
}

.title {
   color: var(--color-title);
}

Определяем переменную, задаем ей начальное значение, при наведении на контейнер — переопределяем. А заголовок просто использует значение переменной, ничего не зная об окружении.

Следующий пример с медиа-запросом:


:root {
   --padding: 20px;
}

.container {
   padding: var(--padding);
}

@media (min-width: 1000px) {
   :root {
       --padding: 50px;
   }
}

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

И наконец, третий пример с svg-иконкой. Та-дам!


<svg>
   <path fill="var(--color-main)" d="M42.5,0.003C19.028,..."/>
</svg>

Да-да, так тоже можно!

Все эти примеры работают, потому что кастомные свойства не исчезают после компиляции, как препроцессорные переменные. Они динамические, они существуют во время выполнения, они живут в DOMе и подчиняются правилам каскада. И это круто. И кстати, кастомные свойства можно получать и задавать в рантайме из JavaScript’а.

Можно ли уже сейчас использовать кастомные свойства? На текущий момент их поддерживают Chrome, Firefox, Safari, Opera и Edge. Поддержки нет в IE и Opera Mini.

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


$color: red;
:root {
    --color: red;
}

.box {
    @supports ((--a: 0)) {
        color: var(--color);
    }
    @supports (not (--a: 0)) {
        color: $color;
    }
}

Можно использовать автоматическую трансформацию, например, с помощью плагина postcss-custom-properties. Но, в отличие от компиляции препроцессором, он позволяет сохранить CSS-переменные и вызовы функции var() для тех браузеров, которые их поддерживают.