Переменные CSS — курс молодого бойца

Автор оригинала: Ahmad Shadeed
  • Перевод
  • Tutorial

Переменные CSS (также известные как настраиваемые свойства) поддерживаются браузерами почти 4 года. Мне нравится применять их в зависимости от проекта или ситуации. Они очень полезны и просты в использовании, но зачастую фронтенд разработчик неправильно использует или неправильно понимает их.

Основная цель статьи: я хочу иметь место, где собрано все, что я знаю о переменных 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;
}

В этом фрагменте #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 {
  /* Other styles */
  background-color: var(--color-primary);
}

Не так много кода, и он чище кода выше, не правда ли? Переменная --color-primary глобальная, потому что определена в элементе :root. Но возможно ограничивать область действия переменных.

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


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

/* Valid names */
:root {
--primary-color: #222;
--_primary-color: #222;
--12-primary-color: #222;
--primay-color-12: #222;
}

/* Invalid names */
:root {
--primary color: #222; /* Spacings are not allowed */
--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, ее новое значение работает только там. Визуальный пример показывает лучше:



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

/* Global variables */
:root {
  --primary-color: #235ad1;
  --unit: 1rem;
}

/* Section title default color and spacing */
.section-title {
  color: var(--primary-color);
  margin-bottom: var(--unit);
}

/* Overrding the section title color */
.featured-authors .section-title {
  --primary-color: #d16823;
}

/* Overrding the section title color & spacing */
.latest-articles .section-title {
  --primary-color: #d12374;
  --unit: 2rem;
}

Резервные значения


Резервное значение не означает, что мы обеспечиваем значение для браузера, который не поддерживает CSS. Эту возможность мы можем использовать с помощью переменных CSS. Посмотрите на пример:

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

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

.section-title {
  color: var(--primary-color, var(--black, #222));
}

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

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


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




Обычное дело — иметь несколько размеров кнопки в дизайн-системе: маленький, средний и большой. Размеры проще описать с помощью переменной CSS.

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

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

.button--large {
  --unit: 1.5rem;
}

Изменяя значение --unit внутри области видимости компонента кнопки мы создаем разные варианты кнопки.

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


HSL — английская аббревиатура: оттенок, насыщенность, яркость.



: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;
}

/* Making the background darker */
.button:hover {
  --primary-b: 33%;
}

Обратите внимание, как темнеет кнопка с уменьшением значения переменной --primary-b.

Если хочется узнать больше о цветах в CSS, я написал о них подробную статью .

Пропорциональное изменение размера


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



Предположим, есть иконка, которая должна быть квадратной. Я определил переменную --size и использовал ее как для ширины, так и для высоты.

.icon {
  --size: 22px;
  width: var(--size);
  height: var(--size);
}

Вот оно! Теперь вы можете имитировать эффект Shift, изменяя только значение переменной --size. Вы можете узнать больше об этом здесь.

CSS Grid


Переменные 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-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;
}



Положение фона


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



.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;
}

/* A class added to the <html> element*/
.dark-mode {
  --text-color: #e9e9e9;
  --border-color: #434343;
  --main-bg-color: #434343;
  --action-bg-color: #363636;
}


Установка значения по умолчанию


Иногда нужно установить переменную CSS с помощью JavaScript . Например, нужно получить высоту расширяемого компонента. Я узнал об этой технике из статьи Майкла Шарнагла.

Переменная --details-height-open пуста. Она добавляется ​​к определенному элементу HTML. Он будет содержать пиксельное значение. Когда по какой-то причине Javascript не работает, важно указать правильное значение по умолчанию или резервное значение.

.section.is-active {
  max-height: var(--details-height-open, auto);
}

Значение auto — это резервное значение на случай сбоя JavaScript и отсутствия переменной CSS --details-height-open.

Управление шириной враппера




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

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

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

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


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

Элементы динамической сетки


Добавляем переменную --item-width внутри атрибута style и все. Такой подход помогает, например, в создании прототипов сеток.

<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;
}

Демо

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




Полезный вариант применения — определение размеров элементов. Допустим, нужно четыре разных размера аватара пользователя с возможностью контроля размера с помощью только одной переменной.

<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);
}

Проанализируем CSS выше:

  • Во-первых, у нас есть var(--size, 1). Я добавил резервное значение на случай, если оно не добавлено в атрибут style.
  • Минимальный размер аватара — 30*30 пикселей.

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


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

:root {
  --gutter: 8px;
}

@media (min-width: 800px) {
  :root {
    --gutter: 16px;
  }
}

Любой элемент с --gutter, изменит отступ в зависимости от размера области просмотра. Разве это не здорово?

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


Да, переменные CSS наследуются. Если в родительском элементе определена переменная, дочерние элементы наследуют ее. Пример:

<div class="parent">
  <p class="child"></p>
</div>

.parent {
  --size: 20px;
}

.child {
  font-size: var(--size);
}

Элемент .child будет иметь доступ к переменной --size в результате наследования ее от своего родителя. Интересно, правда? Как извлечь из этого пользу? Что ж, вот пример из жизни.



У нас есть группа действий с такими требованиями:

  • Возможность изменять размер всех элементов, изменяя только одну переменную.
  • Интервал должен быть динамическим (сжиматься при уменьшении размера элемента и увеличиваться при увеличении размера элемента).

<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 используется для свойства зазора flexbox. Это означает, что интервал может быть динамическим и будет зависеть от переменной --size.

Другой полезный пример — наследование переменных CSS для настройки анимации. Ниже вы видите пример из этой статьи Сандрины Перейры в блоге CSS Tricks.

@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);
}

Я использовал 16 пикселей — это значение свойства цвета. Значение неправильное. Поскольку свойство цвета наследуются, вот, что сделает браузер:

  • Свойство наследуемо?

  • Если да, есть ли у родителя это свойство?

  • Да: значение наследуется
  • Нет: устанавливается по умолчанию

  • Если нет, устанавливается начальное значение

Диаграмма показывает работу браузера:



Некорректное вычисленное значение


Вышеупомянутое технически называется некорректное вычисленное значение. Это происходит, когда var() ссылается на переменную CSS ее начальным значением или использует допустимую переменную CSS с недопустимым значением свойства.

Посмотрим на пример, о котором я узнал из этой статьи пользователя Lea Verou.

.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);
}

Можно задаться вопросом: можно ли интерполировать CSS-переменные с помощью url(). Посмотрим на пример:

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

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

Это невозможно: var(--main-bg) рассматривается как сам URL, который недопустим. К тому моменту, когда браузер вычислит это значение, оно уже не будет действительным и не будет работать, как ожидалось.

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


Полезно то, что возможно хранить несколько значений независимо от значения переменной. Если они невалидны, то это должно сработать. Посмотрим на пример:



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

.section-title {
  color: rgba(var(--main-color), 0.75);
}

В этом примере у нас есть функция rgba(). Значения RGB хранятся в переменной CSS, разделенной запятой. Это может обеспечить гибкость в случае, когда вы хотите настроить прозрачность в зависимости от элемента.

Единственный недостаток — невозможно настроить значение rgba с помощью палитры цветов DevTools. Если это важно для вашего варианта применения или проекта, то вам, возможно, придется пересмотреть использование rgba, как описано выше.

Другой пример — применение этого свойства со свойством background.

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

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

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

У нас есть два раздела. Один из них требует, чтобы фон не повторялся по осям X и Y.

Анимация переменных внутри правила @keyframes


Если вы читали спецификацию для переменных, вы могли прочитать термин animation-tainted — испорченная анимация. Идея в том, что при использовании CSS-переменной внутри правила @keyframes она не может работать в анимации.

<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 анимируется. Это влияет на то, как оно обрабатывается при обращении к нему через функцию var() в свойстве анимации. Когда хочется, чтобы вышеприведенная анимация работала, мы должны анимировать по-старому. То есть нужно заменить переменную фактическим свойством CSS.

@keyframes moveBox {
  0% {
    transform: translateX(0);
  }
  50% {
    transform: translateX(50px);
  }
  100% {
    transform: translateX(100px);
  }
}


Обновление: 9 октября 2020 года


Данни Винтер указал, что можно анимировать CSS-переменные внутри ключевых кадров, регистрируя их с помощью @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);
}

У нас могут быть вариации аватара. Я установил значение по умолчанию в 1, поэтому размер по умолчанию равен (30px * 30px). Обратите внимание на различные вариации классов и на то, как изменение значения --size приводит к изменению размера аватара.

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

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

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

В DevTools есть несколько полезных трюков, чтобы облегчить нам работу с переменными CSS. Давайте исследуем их!

Возможность увидеть значение цвета


Разве не полезно видеть визуальный индикатор для значения цвета или фона, когда вы используете переменную CSS? Chrome и Edge показывают цвет.



Вычисленные значения


Чтобы увидеть вычисленное значение, наведите указатель мыши или кликните — в зависимости от браузера.



Все вычисленные значения отображаются при наведении курсора. За исключением Safari, где для этого нужно нажать кнопку с двумя линиями.

Автозавершение переменных CSS


В большом проекте трудно запомнить все имена переменных. Но можно ввести --, какие-то символы и получить список с переменными CSS на странице. Это работает в Chrome, Firefox и Edge.

Отключение переменной


Когда вам нужно отключить переменную CSS от всех использующих ее элементов, это можно сделать, сняв флажок с элемента, для которого она определена. Смотрите рисунок ниже:



Заключение


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

Я пишу электронную книгу


Рад сообщить вам, что пишу электронную книгу об отладке CSS.



Если вам интересно, перейдите на debuggingcss.com и подпишитесь на обновления о книге.

image




Читать еще


SkillFactory
Школа Computer Science. Скидка 10% по коду HABR

Комментарии 9

    –1

    Отличная новость (для меня)! Год уже не верстаю, но раньше как-то не сталкивался с переменным, наверное, из-за поддержки маленькой.
    В общем, очень рад, что меняется css и становится более гибким. Потому что сам сталкивался с массовой заменой значений.

      0
      для полного счастья — было бы хорошо увидеть управление этими переменными из js.
      очень удобно получается
      но либо автор не задумался над этим, либо переводчик просмотрел.
        0
        Под заголовком «Установка значения по умолчанию» есть аж 2 ссылки на это, нет?
          0
          если что-то описывать — так полностью, а так — под заголовком можно просто ссылки дать… и огромная статья готова.
          0
          document.documentElement.style.setProperty('--variable', 'value')
          0
          Т.е. это в ванильном CSS нынче поддерживается? Вроде несколько лет назад это только во всяких кастомных надстройках…
            0
            уже 4 года в firefox и chrome
            0
            CSS развивается и это прекрасно, будет чудесно если он разовьется до уровня когда можно будет выкинуть жаба скрипт…
              0
              Ага, а HTML разовьется до уровня С++

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое