Переменные в CSS или на английском CSS Custom Properties, что в переводе звучит, как пользовательские свойства CSS, - это специальные css-свойства, которые позволяют изменять значения любых других css-свойств динамически. CSS переременные подобны переменным в JavaScript. Сейчас css-переменные поддерживаются всеми современными браузерами. Можно сказать, что их появление было продиктовано еще препроцессорами, и то, что они были добавлены в спецификацию CSS - большой шаг вперед.
Переменные в CSS, или пользовательские свойства, имеют огромный потенциал для изменения кода и структуры CSS. Давайте рассмотрим стратегии получения максимальной пользы от переменных CSS.
Схожесть css-переменных с переменными в препроцессорах
Пользовательские свойства немного похожи на переменные в препроцессорах, но имеют некоторые важные отличия. Первое и самое очевидное отличие - это синтаксис.
В синтаксисе SASS (SCSS) мы используем символ доллара для обозначения переменной:
1 |
$red-color: #d33a2c; |
Для Less нужно использовать символ @
:
1 |
@red-color:#d33a2c |
Однако, препроцессорные переменные имеют некоторые ограничения:
- Их нельзя изменять динамически.
- Они не знают о структуре DOM.
- Они не могут быть прочитаны или изменены с помощью JavaScript.
Поэтому стоит изучить, что такое переменные CSS, и затем использовать их в коде.
CSS-переменные имеют синтаксис, похожий на переменные в SASS или Less, и используют префикс в виде 2-х дефисов:
1 |
:root { --red-color: #d33a2c; } |
Значение свойства может быть любым допустимым значением CSS: цветом, строкой, значением макета, даже выражением.
Чтобы добавить использование переменной в код, необходимо записать так:
1 2 3 |
.some-text { color: var(--red-color); } |
Еще несколько вариантов назначения css-переменных. Первый из них подразумевает использование глобальной видимости переменных, которые назначаются для :root
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
:root{ --main-color: #4d4e53; --main-bg: rgb(255, 255, 255); --logo-border-color: rebeccapurple; --header-height: 68px; --content-padding: 10px 20px; --base-line-height: 1.428571429; --transition-duration: .35s; --external-link: "external link"; --margin-top: calc(2vh + 20px); /* CSS-переменные можно повторно использовать позже, скажем, в JavaScript */ --foo: if(x > 5) this.width = 10; } |
Что касается элемента :root
, то в HTML это то же самое, что и корневой тег html
, но с более высокой специфичностью.
Для использования CSS-переменных необходимо указать в соответствующем свойстве функцию var()
с нужной переменной в скобках:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
body { color:var(--main-color); background-color: var(--main-bg); } .logo { border: 2px solid var(--logo-border-color); } header { height: var(--header-height); } .content { padding: var(--content-padding); line-height: var(--base-line-height); } |
Вы можете объявлять переменные внутри любого элемента. Тогда они будут привязаны к этому элементу и его дочерним элементам. Использовать переменные можно следующим образом:
1 2 3 4 5 6 7 8 9 10 11 |
.box{ --box-color:#900; --box-padding: 30px 20px; padding: var(--box-padding); } .box div{ color: var(--box-color); border: 2px dotted var(--box-color); } |
Результат:
В примере ниже мы используем переменную из класса .box
в классе .my-test
, в который вложен .box
. И не увидим, что эта переменная не применилась, т.к. для родительского .my-test
она не видна.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<style> .box{ --box-color:#900; --box-padding: 30px 20px; padding: var(--box-padding); } .box div { color: var(--box-color); border: 2px dotted var(--box-color); } .my-test { border: 3px double var(--box-color); } </style> <div class="my-test"> <div class="box"> <div>Тестовый div</div> </div> </div> |
Результат работы переменной не виден:
На скриншоте видно, что в свойстве переменная выделена серым цветом. Это значит, что она не была найдена и использована браузером.
Для вложенного элемента цвет переменной в инспекторе свойств другой - и мы видим на странице, что к нему добавилась точечная рамка.
Особенности объявления css-переменных и их использования
Функция var()
- это удобный способ предоставить значение по умолчанию. Вы можете сделать это, если не уверены, определено ли настраиваемое свойство и хотите ли вы указать значение, которое будет использоваться в качестве резерва. Это можно сделать легко, передав второй параметр функции:
1 2 3 4 5 6 7 |
.box{ --box-color:#4d4e53; --box-padding: 0 10px; /* будет использовано значение 10px, т.к. переменная --box-margin не определена */ margin: var(--box-margin, 10px); } |
Кроме того, вы можете повторно использовать другие переменные для объявления новых:
1 2 3 4 5 6 7 |
.box{ /* Переменная --main-padding будет использована, если --box-padding не определена. */ padding: var(--box-padding, var(--main-padding)); --box-text: 'This is my box'; /* Соответствует тексту --box-highlight-text:'This is my box with highlight'; */ --box-highlight-text: var(--box-text)' with highlight'; } |
Арифметические операции: +, -, * и функция calc()
Поскольку мы привыкли к препроцессорам и другим языкам, мы хотим иметь возможность использовать базовые операторы при работе с переменными. Для этого CSS предоставляет функцию calc()
, которая заставляет браузер пересчитать выражение после внесения каких-либо изменений в значение настраиваемого свойства:
1 2 3 4 5 6 7 8 |
:root{ --indent-size: 10px; --indent-xl: calc(2*var(--indent-size)); --indent-l: calc(var(--indent-size) + 2px); --indent-s: calc(var(--indent-size) - 2px); --indent-xs: calc(var(--indent-size)/2); } |
Вы можете столкнуться с проблемами, если попытаетесь использовать значение без единицы. В этой ситуации вам не обойтись без функции calc()
- она ваш друг, потому что без нее код не будет работать:
1 2 3 4 5 6 7 8 |
:root{ --spacer: 10; } .box{ padding: var(--spacer)px 0; /* НЕ РАБОТАЕТ */ padding: calc(var(--spacer)*1px) 0; /* РАБОТАЕТ */ } |
Рассмотрим пример, в котором мы можем изменить размер шрифта кнопок, исходя из одной переменной и используя функцию calc()
для расчета других величин на основе нашей единственной переменной:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
button { --button-sml: 1em; display: inline-block; background-color: #d56; padding: 8px 35px; border: 1px solid #900; border-radius: 18px; margin: 10px; color: #600; cursor: pointer; font-size: var( --button-sml ); } .btn-medium { font-size: calc( var( --button-sml )*1.3 ); } .btn-large{ font-size: calc( var( --button-sml )*1.8 ); } |
Попробуйте самостоятельно изменить шрифт, изменяя значения в CSS.
See the Pen Размер кнопок в зависимости от css-переменной by Elen (@ambassador) on CodePen.
Области видимости и наследование
Прежде чем говорить о областях пользовательских свойств CSS, давайте вспомним области JavaScript и препроцессоры, чтобы лучше понять различия.
Мы знаем, что, например, с переменными JavaScript, которые определяются ключевым словом var
, область действия ограничена функциями.
Мы имеем аналогичную ситуацию с let
и const
, но они являются локальными переменными масштаба блока.
Замыкание в JavaScript - это функция, которая имеет доступ к внешним (охватывающим) функциям - цепочке областей. Замыкание имеет три цепочки областей и имеет доступ к следующему:
- его собственная область видимости (т. е. переменные, определенные между его фигурными скобками)
- переменные внешней функции,
- глобальные переменные.
История с препроцессорами схожа. Давайте используем SASS в качестве примера, потому что это, вероятно, самый популярный препроцессор сегодня. С SASS мы имеем два типа переменных: локальные и глобальные.
Глобальная переменная может быть объявлена вне любого селектора или конструкции (например, как mixin). В противном случае переменная будет локальной.
Любые вложенные блоки кода могут обращаться к окружающим переменным (как в JavaScript).
Это означает, что в SASS области действия переменной полностью зависят от структуры кода.
Однако пользовательские свойства CSS наследуются по умолчанию, и, как и другие свойства CSS, они каскадные.
Вы также не можете иметь глобальную переменную, которая объявляет пользовательское свойство вне селектора - это не допустимый CSS. Глобальная область действия для пользовательских свойств CSS на самом деле является корневой областью действия, после чего свойство доступно глобально.
Динамическое изменение css-переменных
Давайте использовать наши знания синтаксиса и адаптировать пример SASS к HTML и CSS. Мы создадим демонстрационную версию, используя собственные пользовательские свойства CSS. Во-первых, HTML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<style> :root { --globalVar: 10px; } .enclosing { --enclosingVar: 20px; font-size: var(--enclosingVar); } .enclosing .closure { --closureVar: 30px; font-size: calc(var(--closureVar) + var(--enclosingVar) + var(--globalVar)); /* 60px для элемента */ } </style> global <div class="enclosing"> enclosing <div class="closure"> closure </div> </div> |
Посмотрите на результат:
На скриншоте мы можем увидеть размер шрифта в классе .closure
на основе вычисленных свойств.
Изменения в пользовательских свойствах немедленно применяются ко всем экземплярам.
Интересно, что в CSS вы можете изменить значение переменной уже после того, как она была использована для расчета значения свойства, и при этом вычисленное значение изменяется. Рассмотрим это на примере расчета размера шрифта из предыдущего блока кода и увидим, что значение размера шрифта пересчитывается из измененного значения --closureVar
в 50px:
1 2 3 4 5 6 7 8 |
.enclosing .closure { --closureVar: 30px; font-size: calc(var(--closureVar) + var(--enclosingVar) + var(--globalVar)); /* Теперь 80px , --closureVar: 50px использована */ --closureVar: 50px; } |
Скриншот с вычисленными значениями в Инспекторе свойств: 80px вместо 60px из предыдущего кода.
В SASS или LESS это бы не получилось. Это первое огромное отличие: если вы переназначите значение пользовательского свойства, браузер пересчитает все переменные и выражения calc()
, где они были применены.
Препроцессоры не знают о структуре DOM
Предположим, что мы хотим использовать размер шрифта по умолчанию для блока, кроме случаев, когда присутствует выделенный класс. Тогда нам необходимо указать 2 переменные в скобках через запятую: сначала желаемое значение, затем значение по умолчанию.
Вот HTML- и CSS-код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<style> .highlighted { --highlighted-size: 30px; } .default { --default-size: 10px; /* Используем default-size, кроме случая, когда выделен highlighted-size */ font-size: var(--highlighted-size, var(--default-size)); } </style> <div class="default"> default </div> <div class="default highlighted"> default highlighted </div> |
Поскольку второй элемент HTML с классом default
имеет также класс highlighted
, свойства для класса highlighted
будут применены к этому элементу.
В данном случае это означает, что css-переменная -–highlighted-size: 30px;
будет применена в нижнем div-е, и назначаемое свойство font-size будет использовать именно --highlighted-size
.
Пример вживую:
Если аналогичный пример мы напишем в SASS, то получим несколько иной результат:
See the Pen Font-size with SASS by Elen (@ambassador) on CodePen.
Это происходит потому, что все вычисления и обработка SASS происходят во время компиляции, и, конечно, он ничего не знает о структуре DOM, полностью полагаясь на структуру кода.
В отличие от SASS, пользовательские свойства обладают преимуществами области видимости переменных и добавляют обычный каскад CSS-свойств в зависимости от структуры DOM и следуя тем же правилам, что и другие свойства CSS.
Отсюда вывод: css-переменные знают структуру DOM и являются динамическими.
Ключевые слова и свойство all
для CSS-переменных
Пользовательские свойства CSS подчиняются тем же правилам, что и другие свойства CSS. Это означает, что вы можете назначить им любое из общих ключевых слов CSS:
inherit
(унаследовать). Это ключевое слово CSS применяет значение родительского элемента;initial
(начальное) - используется то значение свойства, которое определено в спецификации CSS (пустое значение или ничего в некоторых случаях );unset
(снять) - устанавливает значение свойства какinherit
, если свойство наследуется от своего родителя, в противном случае значение устанавливается какinitial
.revert
(возврат) - сбрасывает свойство к значению по умолчанию, установленному таблицей стилей браузера пользователя (пустое значение в случае пользовательских свойств CSS). Пока имеет ограниченную поддержку браузерами по данным caniuse.com.
Пример:
1 2 3 4 5 6 |
.common-values{ --border: inherit; --bgcolor: initial; --padding: unset; --animation: revert; } |
Давайте рассмотрим другой случай. Предположим, что вы хотите создать компонент и хотите быть уверенным, что другие стили или пользовательские свойства не будут применены к нему непреднамеренно (в таком случае обычно используется модульное решение CSS для стилей).
Но теперь есть другой способ: использовать css-свойство all
. Это сокращение сбрасывает все свойства CSS. То есть не нужно перечислять весь набор сбрасываемых свойств, а написать следующее:
1 2 3 |
.my-wonderful-clean-component{ all: initial; } |
Это сбросит все стили для нашего компонента.
К сожалению, ключевое слово all
не сбрасывает пользовательские свойства (css-переменные). Таким образом, в будущем полный сброс может быть сделан следующим образом:
1 2 3 4 |
.my-wonderful-clean-component{ --: initial; /* сбрасывает все CSS-переменные */ all: initial; /* сбрасывает все другие CSS стили */ } |
Варианты использования css-переменных
Есть много вариантов использования пользовательских свойств. Рассмотрим самые интересные из них.
Эмулировать несуществующие правила CSS
Имя этих CSS-переменных - «пользовательские свойства», так почему бы не использовать их для эмуляции несуществующих свойств?
Их много: translateX/Y/Z
, background-repeat-x/y
(все еще не совместимы с браузерами), box-shadow-color
.
Давайте попробуем заставить последний работать. В нашем примере давайте изменим цвет рамки тени при наведении. Мы просто хотим следовать правилу DRY (не повторяться), поэтому вместо того, чтобы повторять все значение box-shadow
в разделе :hover
, мы просто изменим его цвет. Пользовательские свойства для спасения:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<style> .shadow-btn{ transition: all 0.3s; margin: 15px; border-radius: 10px; padding: 10px 15px; display: inline-block; background-color: black; color: var(--box-shadow-color); cursor: pointer; } .shadow-btn { --box-shadow-color: yellow; box-shadow: 0 0 30px var(--box-shadow-color); } .shadow-btn:hover { --box-shadow-color: orange; /* Вместо: box-shadow: 0 0 30px orange; */ color: var(--box-shadow-color); } </style> <div class="shadow-btn">Меняем цвет тени</div> |
Посмотрим на результат:
Можно использовать переменные для добавления изображений, в том числе, и фоновых:
See the Pen CSS custom-properties via attribute by Burmese Potato (@BurmesePotato) on CodePen.
Цветовые схемы
Один из наиболее распространенных вариантов использования пользовательских свойств - для цветовых схем в приложениях. Пользовательские свойства были созданы для решения именно такой проблемы. Итак, давайте предоставим простую цветовую схему для компонента (те же шаги могут быть выполнены для приложения).
Вот код для нашего компонента кнопки:
1 2 3 4 5 6 7 8 |
.my-btn { background-image: linear-gradient(to bottom, #3498db, #2980b9); text-shadow: 1px 1px 3px #777; box-shadow: 0px 1px 3px #777; border-radius: 28px; color: #ffffff; padding: 10px 20px 10px 20px; } |
Давайте предположим, что мы хотим инвертировать цветовую тему.
Первым шагом будет расширение всех цветовых переменных до пользовательских свойств CSS и перезапись нашего компонента. Итак, используем ряд переменных и запишем свойства с их использованием. Результат будет таким же:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<style> .my-btn { --shadow-color: #777; --gradient-from-color: #3498db; --gradient-to-color: #2980b9; --color: #ffffff; background-image: linear-gradient( to bottom, var(--gradient-from-color), var(--gradient-to-color) ); text-shadow: 1px 1px 3px var(--shadow-color); box-shadow: 0px 1px 3px var(--shadow-color); border-radius: 28px; color: var(--color); padding: 10px 20px 10px 20px; cursor: pointer; display: inline-block; } </style> <span class="my-btn">button</span> |
В этом коде есть все переменные, которые нужны для переопределения цветов на инвертированные значения. Мы могли бы, например, добавить класс inverted
в HTML (скажем, к элементу section
) и изменить цвета, когда он применяется:
1 2 3 4 5 6 |
section.inverted .my-btn{ --shadow-color: #444; --gradient-from-color: #ff6724; --gradient-to-color: #ff7F46; --color: #000000; } |
Попробуйте сами с помощью примера ниже:
See the Pen Инвертируем цвета с помощью css-переменных by Elen (@ambassador) on CodePen.
Такое поведение не может быть достигнуто в препроцессоре CSS без дублирования кода. С препроцессором вам всегда нужно переопределять фактические значения и правила, что всегда приводит к дополнительному CSS.
Благодаря CSS-переманным решение максимально чистое, а копирование и вставка исключаются, поскольку переопределяются только значения переменных.
Использование цветовых функций
Для создания цветовых схем в препроцессорах можно использовать специальные функции для затемнения, осветления или обесцвечивания:
1 2 3 |
darken($base-color, 10%); lighten($base-color, 10%); desaturate($base-color, 20%); |
Мы можем заменить их css-переменными, используя функцию calc()
и представление цвета в системе hls()
или rgb()
. Рассмотрим пример с цветами в системе rgb()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
:root { --r: 255; --g: 2; --b: 2; } .btn-red { background-color: rgb(var(--r), var(--g), var(--b)); border-color: rgb( calc(var(--r) - 40 ), calc( var(--g)*25 ), calc( var(--b) * 25) ); } .btn-red:hover { background-color: rgb(calc(var(--r) - 50 ), var(--g), var(--b)); border-color: rgb( calc(var(--r) - 70 ), calc( var(--g) + 20 ), calc( var(--b) * 3) ); color: #fff; } |
Пример с несколькими цветными кнопками:
See the Pen Цветовые изменения с помощью css-переменных by Elen (@ambassador) on CodePen.
Использование пользовательских свойств с JavaScript
Раньше для отправки данных из CSS в JavaScript нам часто приходилось прибегать к хитростям, записывая значения CSS через простой JSON в выводе CSS, а затем читая их из JavaScript.
Теперь мы можем легко взаимодействовать с CSS-переменными из JavaScript, читая и записывая их, используя хорошо известные методы getPropertyValue()
и setProperty()
, которые используются для обычных свойств CSS. Ниже приведены 2 функции, которые можно использовать для чтения и назначения css-переменных из JavaScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/** * Возвращает значение пользовательского свойства CSS, примененное к элементу * element {Element} * varName {String} без '--' * * Например: * readCssVar(document.querySelector('.box'), 'color'); */ function readCssVar(element, varName){ const elementStyles = getComputedStyle(element); return elementStyles.getPropertyValue(`--${varName}`).trim(); } /** * Записываетa пользовательского свойства CSS для элемента * element {Element} * varName {String} без '--' * * Например: * writeCssVar(document.querySelector('.box'), 'color', 'white'); */ function writeCssVar(element, varName, value){ return element.style.setProperty(`--${varName}`, value); } |
В примере ниже от автора Darko css-переменная --ratio
используется для расчета размера фотографии для размещения фотогалереи из разноразмерных фотографий в одну строку:
See the Pen Google Photos grid (minimal JS) by Darko (@DarkoKukovec) on CodePen.
Еще мы можем использовать переменые для директивы @media
. Предположим, у нас есть список значений медиа-запроса:
1 2 3 4 |
.breakpoints-data { --phone: 480px; --tablet: 800px; } |
Поскольку мы хотим использовать их только в JavaScript - например, в Window.matchMedia() - мы можем легко получить их из CSS:
1 2 3 4 5 6 7 |
// получаем ссылку на элемент const breakpointsData = document.querySelector('.breakpoints-data'); // получаем значение css-переменной const phoneBreakpoint = getComputedStyle(breakpointsData) .getPropertyValue('--phone'); |
Пример изменения css-свойств с помощью css-переменных и JavaScript
See the Pen Update CSS Variables with JS by Elen (@ambassador) on CodePen.
В примере ниже от Lisi вы найдете использование множества переменных и в CSS, и в JavaScript.
See the Pen Single Div Animation by Lisi (@lisilinhart) on CodePen.
Пример переключения светлой/темной темы с помощью CSS-переменных
Автор Joe. При переключении мы видим анимацию.
See the Pen Article Toggle by Joe (@dope) on CodePen.
На основе статей It's Time To Start Using CSS Custom Properties и A Strategy Guide To CSS Custom Properties