CSS переменные (пользовательские CSS-свойства)

CSS-переменные (их ещё называют «пользовательскими свойствами») поддерживаются веб-браузерами уже почти четыре года. Я пользуюсь ими там, где они могут пригодиться. Это зависит от проекта, над которым я работаю, и от конкретных задач, которые мне приходится решать. Работать с CSS-переменными просто, они способны принести разработчику немалую пользу. Правда, часто фронтенд-программисты применяют CSS-переменные неправильно или не понимают особенностей работы с ними. Я написал эту статью для того чтобы собрать в ней всё, что я знаю о CSS-переменных. В ходе работы над ней я надеялся узнать о них что-то новое и упорядочить то, что мне уже известно. Вы найдёте в этом материале всё, что нужно знать о CSS-переменных. Здесь имеется множество практических примеров и описаний сценариев использования CSS-переменных. Готовы? Если так — приступим.

Введение

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

.section {
  border: 2px solid #235ad1;
}

.section-title {
  color: #235ad1;
}

.section-title::before {
  content: "";
  display: inline-block;
  width: 20px;
  height: 20px;
  background-color: #235ad1;
}
В этом примере CSS-кода значение #235ad1 используется три раза. Представьте себе, что это — часть большого проекта. В нём подобные стили разбросаны по множеству CSS-файлов. Вас попросили поменять цвет #235ad1. В такой ситуации лучшее, что можно сделать, заключается в том, чтобы прибегнуть к возможностям редакторов кода по поиску и замене строковых значений. Но при использовании CSS-переменных подобные задачи решаются гораздо проще. Давайте поговорим о том, как объявлять CSS-переменные. Перед именем переменной должно идти два дефиса. Объявим переменную в стиле для псевдокласса :root элемента <html>:

:root {
  --color-primary: #235ad1;
}

.section {
  border: 2px solid var(--color-primary);
}

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

.section-title::before {
  /* Другие стили */
  background-color: var(--color-primary);
}
По-моему, этот фрагмент кода выглядит гораздо чище, чем предыдущий. Переменная --color-primary является глобальной, так как объявлена она в стиле для псевдокласса :root. Но CSS-переменные можно объявлять и на уровне отдельных элементов, ограничивая область их видимости в документе.

Именование переменных

Правила именования CSS-переменных не особенно сильно отличаются от правил, принятых в различных языках программирования. А именно, правильное имя CSS-переменной может включать в себя алфавитно-цифровые символы, знаки подчёркивания и дефисы. Кроме того, стоит обратить внимание на то, что имена этих переменных чувствительны к регистру.

/* Правильные имена */
:root {
  --primary-color: #222;
  --_primary-color: #222;
  --12-primary-color: #222;
  --primay-color-12: #222;
}

/* Неправильные имена */
:root {
  --primary color: #222; /* Пробелы использовать нельзя */
  --primary$%#%$#
}

Область видимости переменных

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

let element = "cool";

function cool() {
  let otherElement = "Not cool";
  console.log(element);
}
В этом примере переменная element является глобальной, она доступна в функции cool(). Но к переменной otherElement можно обратиться только из тела функции cool(). Рассмотрим эту идею в применении к CSS-переменным.

:root {
  --primary-color: #235ad1;
}

.section-title {
  --primary-color: d12374;
  color: var(--primary-color);
}
Переменная --primary-color является глобальной, обратиться к ней можно из любого элемента документа. Если переопределить её в блоке .section-title, это приведёт к тому, что её новым значением можно будет пользоваться только в этом блоке. Вот схема, разъясняющая эту идею.

Область видимости CSS-переменных

Тут показана переменная --primary-color, используемая для настройки цвета заголовков разделов. Нам нужно настроить цвет заголовков разделов со сведениями об избранных авторах и о свежих статьях. Поэтому в стилях этих разделов мы переопределяем данную переменную. То же самое происходит и с переменной --unit. Вот стили, на которых основана предыдущая схема.

/* Глобальные переменные */
:root {
  --primary-color: #235ad1;
  --unit: 1rem;
}

/* Для цвета и отступа применяются глобальные значения */
.section-title {
  color: var(--primary-color);
  margin-bottom: var(--unit);
}

/* Переопределение переменной, управляющей цветом */
.featured-authors .section-title {
  --primary-color: #d16823;
}

/* Переопределение переменных, управляющими цветом и отступом */
.latest-articles .section-title {
  --primary-color: #d12374;
  --unit: 2rem;
}

Использование резервных значений

Обычно «резервные значения» используются для обеспечения работоспособности сайтов в браузерах, которые не поддерживают некие современные CSS-механизмы. Но здесь речь пойдёт не об этом, а о том, как задавать значения, используемые в том случае, если нужные CSS-переменные оказываются недоступными. Рассмотрим следующий пример:

.section-title {
  color: var(--primary-color, #222);
}
Обратите внимание на то, что функции var() передано несколько значений. Второе из них, #222, будет использовано только в том случае, если переменная --primary-color не будет определена. При указании резервных значений можно использовать и вложенные конструкции var():

.section-title {
  color: var(--primary-color, var(--black, #222));
}
Этот подход к работе с переменными может оказаться полезным в том случае, если значение переменной зависит от некоего действия. Если может случиться так, что в переменной не будет значения, важно предусмотреть использование резервного значения.

Примеры и сценарии использования CSS-переменных

Управление размером компонентов

В дизайн-системах часто имеются, например, кнопки разных размеров. Как правило, речь идёт о трёх вариантах размеров (маленький, обычный, большой). С использованием CSS-переменных можно очень легко описывать такие кнопки и другие подобные элементы.

.button {
  --unit: 1rem;
  padding: var(--unit);
}

.button--small {
  --unit: 0.5rem;
}

.button--large {
  --unit: 1.5rem;
}
Меняя значение переменной --unit в области видимости, соответствующей компоненту кнопки, мы создаём разные варианты кнопки.

CSS-переменные и HSL-цвета

HSL (Hue, Saturation, Lightness — тон, насыщенность, светлота) — это цветовая модель, в которой компонент H определяет цвет, а компоненты S и L определяют параметры насыщенности и светлоты цвета.

:root {
  --primary-h: 221;
  --primary-s: 71%;
  --primary-b: 48%;
}

.button {
  background-color: hsl(var(--primary-h), var(--primary-s), var(--primary-b));
  transition: background-color 0.3s ease-out;
}

/* Затемнение фона */
.button:hover {
  --primary-b: 33%;
}
Обратите внимание на то, как я сделал цвет кнопки темнее, уменьшив значение переменной --primary-b.

Изменение размеров элементов с сохранением пропорций

Если вы работали в какой-нибудь дизайнерской программе, вроде Photoshop, Sketch, Figma или Adobe XD, то вы можете знать об использовании клавиши Shift при изменении размеров объектов. Благодаря этому приёму можно избежать искажения пропорций элементов. В CSS нет стандартного механизма изменения размеров элементов с сохранением пропорций. Но это ограничение можно обойти, воспользовавшись, как несложно догадаться, CSS-переменными. Предположим, что у нас имеется значок, ширина и высота которого должны быть одинаковыми. Для того чтобы этого добиться, я определил CSS-переменную --size, использовав её для настройки ширины и высоты элемента.

.icon {
  --size: 22px;
  width: var(--size);
  height: var(--size);
}
В результате оказывается, что этот приём имитирует использование клавиши Shift при изменении размеров объектов. Тут достаточно изменить значение одной переменной --size. Подробнее эта тема освещена здесь.

Макеты, основанные на CSS Grid

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

.wrapper {
  --item-width: 300px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--item-width), 1fr));
  grid-gap: 1rem;
}

.wrapper-2 {
  --item-width: 500px;
}
Благодаря такому подходу можно создать гибкий Grid-макет, подходящий для использования в различных проектах, который легко поддерживать. Ту же идею можно применить и для настройки свойства grid-gap.

.wrapper {
  --item-width: 300px;
  --gap: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--item-width), 1fr));
}

.wrapper.gap-1 {
  --gap: 16px;
}

Хранение в переменных значений со сложной структурой CSS-градиенты

Под «значениями со сложной структурой» я понимаю, например, нечто вроде градиентов. Если в проекте есть градиент или фон, используемые во многих местах этого проекта, то их описания имеет смысл хранить в CSS-переменных.

:root {
  --primary-gradient: linear-gradient(150deg, #235ad1, #23d1a8);
}

.element {
  background-image: var(--primary-gradient);
}
В подобных ситуациях в переменных можно хранить и отдельные элементы «сложных» значений. Это, например, может быть угол градиента:

.element {
  --angle: 150deg;
  background-image: linear-gradient(var(--angle), #235ad1, #23d1a8);
}

.element.inverted {
  --angle: -150deg;
}

Позиция фона

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

.table {
  --size: 50px;
  --pos: left center;
  background: #ccc linear-gradient(#000, #000) no-repeat;
  background-size: var(--size) var(--size);
  background-position: var(--pos);
}

Переключение между тёмной и светлой темами

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

:root {
  --text-color: #434343;
  --border-color: #d2d2d2;
  --main-bg-color: #fff;
  --action-bg-color: #f9f7f7;
}

/* Класс, добавленный к элементу <html> */
.dark-mode {
  --text-color: #e9e9e9;
  --border-color: #434343;
  --main-bg-color: #434343;
  --action-bg-color: #363636;
}

Установка значений, применяемых по умолчанию

В некоторых ситуациях нужно задавать CSS-переменные с использованием JavaScript. Представим, что нам нужно установить значение свойства height элемента, размеры которого могут меняться. Я узнал о данном приёме из этой статьи. Переменная --details-height-open изначально пуста. Её планируется использовать в описании стиля некоего элемента. Она должна содержать высоту элемента в пикселях. Если из JavaScript установить значение этой переменной по какой-то причине не удастся, важно предусмотреть применение вместо неё некоего резервного значения, используемого по умолчанию.

.section.is-active {
  max-height: var(--details-height-open, auto);
}
В этом примере роль значения, используемого по умолчанию, играет auto. Оно будет применено в том случае, если из JavaScript не удастся задать значение переменной --details-height-open.

Регулирование ширины элемента-контейнера

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

.wrapper {
  --size: 1140px;
  max-width: var(--size);
}

.wrapper--small {
  --size: 800px;
}

Встроенные стили

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

Динамические Grid-элементы

Например, для настройки ширины элемента можно воспользоваться переменной --item-width, объявленной прямо в атрибуте элемента style. Такой подход может оказаться полезным при прототипировании Grid-макетов. Вот HTML-код элемента:

<div class="wrapper" style="--item-width: 250px;">
  <div></div>
  <div></div>
  <div></div>
</div>
Вот стиль, применяемый к этому элементу:

.wrapper {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--item-width), 1fr));
  grid-gap: 1rem;
}
Здесь можно поэкспериментировать с примером к этому разделу.

Аватары пользователей

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

<img src="user.jpg" alt="" class="c-avatar" style="--size: 1" />
<img src="user.jpg" alt="" class="c-avatar" style="--size: 2" />
<img src="user.jpg" alt="" class="c-avatar" style="--size: 3" />
<img src="user.jpg" alt="" class="c-avatar" style="--size: 4" />
Вот стили:

.c-avatar {
  display: inline-block;
  width: calc(var(--size, 1) * 30px);
  height: calc(var(--size, 1) * 30px);
}
Проанализируем эти стили: У нас имеется конструкция var(--size, 1). В ней предусмотрено значение, применяемое по умолчанию. Оно используется в том случае, если значение переменной --size не задано с использованием атрибута style стилизуемого элемента. Минимальный размер элемента устанавливается равным 30px*30px.

Медиазапросы

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

:root {
  --gutter: 8px;
}

@media (min-width: 800px) {
  :root {
    --gutter: 16px;
  }
}
В результате свойства любого элемент, в котором используется переменная --gutter, будут зависеть от ширины области просмотра браузера. Как по мне, так это — просто замечательная возможность.

Наследование

CSS-переменные поддерживают наследование. Если в пределах родительского элемента объявлена CSS-переменная, то элементы-потомки наследуют эту переменную. Рассмотрим пример. Вот HTML-код:

<div class="parent">
  <p class="child"></p>
</div>
Вот стили:

.parent {
  --size: 20px;
}

.child {
  font-size: var(--size);
}
Элемент .child наследует переменную --size, объявленную в стиле элемента .parent. У элемента .child есть доступ к этой переменной. Полагаю, это — очень интересно. Возможно, вы сейчас задаётесь вопросом о том, какая нам от этого польза. Полагаю, ответить на этот вопрос поможет следующий пример из жизни. Тут имеется группа кнопок, к которым предъявляются следующие требования: - Возможность изменения размера всех элементов путём установки значения единственной переменной. - Расстояние между элементами должно динамически меняться в зависимости от их размера. При увеличении элементов расстояние между ними увеличивается, а при уменьшении — уменьшается. Вот разметка к этому примеру:

<div class="actions">
  <div class="actions__item"></div>
  <div class="actions__item"></div>
  <div class="actions__item"></div>
</div>
Вот стили:

.actions {
  --size: 50px;
  display: flex;
  gap: calc(var(--size) / 5);
}

.actions--m {
  --size: 70px;
}

.actions__item {
  width: var(--size);
  height: var(--size);
}
Обратите внимание на то, как я использовал переменную --size при настройке свойства gap Flexbox-элементов. Это позволяет, основываясь на переменной --size, динамически менять расстояние между элементами Ещё один пример использования механизма наследования CSS-переменных представлен настройкой CSS-анимаций. Этот пример я взял отсюда.

@keyframes breath {
  from {
    transform: scale(var(--scaleStart));
  }
  to {
    transform: scale(var(--scaleEnd));
  }
}

.walk {
  --scaleStart: 0.3;
  --scaleEnd: 1.7;
  animation: breath 2s alternate;
}

.run {
  --scaleStart: 0.8;
  --scaleEnd: 1.2;
  animation: breath 0.5s alternate;
}
При таком подходе нам не нужно дважды объявлять @keyframes. В стилях .walk и .run переопределяются значения унаследованных переменных.

Валидация CSS-переменных

Если оказывается, что с CSS-переменной, переданной функции var(), что-то не так, браузер заменит значение этой переменной на исходное (унаследованное) значение соответствующего свойства.

:root {
  --main-color: 16px;
}

.section-title {
  color: var(--main-color);
}
Здесь в переменную --main-color, используемую для настройки свойства color, записано значение 16px. А это — совершенно неправильно. Свойство color наследуется. Браузер в этой ситуации работает по следующему алгоритму: Является ли свойство наследуемым? Вот схема работы браузера.

Концепция недопустимого значения, появляющегося во время вычислений

То, о чём шла речь выше, с технической точки зрения, называется «недопустимым значением, появляющимся во время вычислений» (Invalid At Computed-Value Time). Ситуация, в которой появляются такие значения, возникает, когда функции var() передаётся корректная CSS-переменная, значение которой не подходит для записи в настраиваемое с её помощью свойство. Рассмотрим следующий пример, который я взял из этой статьи:

.section-title {
  top: 10px;
  top: clamp(5px, var(--offset), 20px);
}
Если браузер не поддерживает функцию clamp() — воспользуется ли он, в качестве резервного, значением, заданным в конструкции top: 10px? Если кратко ответить на этот вопрос, то нет — не воспользуется. Причина этого заключается в том, что к тому моменту, когда браузер обнаружит некорректное значение, которое пытаются записать в свойство, он уже отбросит, в соответствии с порядком каскадного применения стилей, другие значения. То есть — он просто проигнорирует конструкцию top: 10px. Вот что говорится об этом в спецификации CSS: Концепция появления недопустимого значения во время вычислений существует из-за того, что ошибки, связанные с переменными, не проявляются, в отличие от других синтаксических ошибок, на ранних стадиях работы системы. Поэтому оказывается, что когда пользовательский агент обнаружит, что значение переменной некорректно, он уже отбросит, в соответствии с порядком каскадного применения стилей, другие значения. В результате оказывается, что если нужно применять возможности CSS, не пользующиеся широкой поддержкой браузеров, при реализации которых используются CSS-переменные, нужно применять директиву @supports. Вот как это сделано в вышеупомянутой статье:

@supports (top: max(1em, 1px)) {
  #toc {
    top: max(0em, 11rem - var(--scrolltop) * 1px);
  }
}

Интересные находки Хранение URL в переменных

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

:root {
  --main-bg: url("https://example.com/cool-image.jpg");
}

.section {
  background: var(--main-bg);
}
Тут может возникнуть вопрос о том, можно ли обрабатывать конструкции вида var(--main-bg) с помощью CSS-функции url(). Рассмотрим следующий пример:

:root {
  --main-bg: "https://example.com/cool-image.jpg";
}

.section {
  background: url(var(--main-bg));
}
Так поступить не получится, так как функция url() воспринимает всю конструкцию var(--main-bg) в виде URL, а это неправильно. К тому моменту, когда браузер вычислит значение, оно уже будет некорректным, рассмотренная конструкция не будет работать так, как ожидается.

Хранение нескольких значений

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

:root {
  --main-color: 35, 90, 209;
}

.section-title {
  color: rgba(var(--main-color), 0.75);
}
Здесь имеется функция rgba() и RGB-значения, разделённые запятой и записанные в CSS-переменную. Эти значения используются при задании цвета. При таком подходе к использованию функции rgba() у разработчика появляется возможность воздействовать на значение, соответствующее альфа-каналу цвета, настраивая цвет различных элементов. Единственный минус такого подхода заключается в том, что цвет, задаваемый функцией rgba(), нельзя будет настраивать, пользуясь инструментами разработчика браузера. Если при работе над вашим проектом эта возможность важна — вам, вероятно, не подойдёт вышеописанный способ использования функции rgba(). Вот пример использования CSS-переменной для настройки свойства background:

:root {
  --bg: linear-gradient(#000, #000) center/50px;
}

.section {
  background: var(--bg);
}

.section--unique {
  background: var(--bg) no-repeat;
}
Здесь показана стилизация двух разделов сайта. Фон одного из них не должен повторяться по осям x и y.

Изменение значений CSS-переменных в теле правила @keyframes

Если вы читали спецификацию, посвящённую CSS-переменным, то вы могли столкнуться там с термином «animation-tainted». С его помощью описывают тот факт, что значения CSS-переменных не поддаются плавному изменению в правиле @keyframes. Рассмотрим пример. Вот HTML-код:

<div class="box"></div>
Вот стили:

.box {
  width: 50px;
  height: 50px;
  background: #222;
  --offset: 0;
  transform: translateX(var(--offset));
  animation: moveBox 1s infinite alternate;
}

@keyframes moveBox {
  0% {
    --offset: 0;
  }
  50% {
    --offset: 50px;
  }
  100% {
    --offset: 100px;
  }
}
Анимация в этом случае не будет плавной. Переменная примет лишь три значения: 0, 50px и 100px. В спецификации CSS говорится, что любое пользовательское свойство, использованное в правиле @keyframes, становится animation-tainted-свойством, что влияет на то, как оно обрабатывается посредством функции var() при анимировании элементов. Если нам нужно обеспечить в предыдущем примере плавную анимацию, то делать это надо так, как делалось раньше. То есть — надо заменить переменную на те CSS-свойства элемента, которые нужно анимировать.

@keyframes moveBox {
  0% {
    transform: translateX(0);
  }
  50% {
    transform: translateX(50px);
  }
  100% {
    transform: translateX(100px);
  }
}
Хочу отметить, что после публикации этой статьи мне сообщили о том, что анимировать CSS-переменные в @keyframes, всё же, можно. Но для этого переменные надо регистрировать с использованием правила @property. Пока эта возможность поддерживается лишь браузерами, основанными на Chromium.

@property --offset {
  syntax: "<length-percentage>";
  inherits: true;
  initial-value: 0px;
}

Вычисления

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

.c-avatar {
  display: inline-block;
  width: calc(var(--size, 1) * 30px);
  height: calc(var(--size, 1) * 30px);
}
Размеры аватара зависят от значения переменной --size. Значением, используемым по умолчанию, является 1. Это значит, что стандартным размером аватара является 30px*30px. Обратите внимание на следующие стили и на то, что изменение этой переменной приводит к изменению размера аватара.

.c-avatar--small {
  --size: 2;
}

.c-avatar--medium {
  --size: 3;
}

.c-avatar--large {
  --size: 4;
}

Итоги

Я рассказал довольно много всего о CSS-переменных. Надеюсь, вам пригодится то, что вы сегодня узнали.

04.03.2023

2439
A B i U S JS

PHP HTML CSS
Чат
    Для входа только имэйл или имя и апроль
    Можно сменить аватар
    Имэйл Ваше имя
    Пароль