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

Мы используем переменные для того, чтобы сохранять размеры шрифта, значения цвета, параметры разметки. Но у переменных в препроцессорах имеются ограничения:

  • они не могут изменяться динамически
  • они не зависят от структуры DOM
  • к ним нет доступа из Javascript

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

Пользовательские свойства – это новые горизонты в веб разработке.

Синтаксис объявления и использования пользовательских свойств

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

Каждый препроцессор требует собственный синтаксис для объявления переменных. Обычно объявление начинается с зарезервированного символа – например $ в SASS и @ в LESS.

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

Вы можете поинтересоваться, “А почему бы не использовать существующий синтаксис?”

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

(Что касается названия: В связи с тем что идеи и цели CSS свойств очень похожи их часто называют CSS переменными, хотя правильное название это именно  CSS свойства и, читая дальше, вы поймете почему это название им подходит лучше)

И так, для объявления переменной вместо обычного CSS свойства такого как color или  padding, просто объявите пользовательское свойство добавив в качестве префикса два минуса –:

   .box {
       --box-color: #4d4e53;
       --box-padding: 0 10px;
    }

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

Вот пример использования валидных значение для пользовательских свойств:

: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 только с большeй specificity.

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

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

.box{
  --box-color:#4d4e53;
  --box-padding: 0 10px;

  padding: var(--box-padding);
}

.box div{
  color: var(--box-color);
}

Объявление использование

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

.box{
  --box-color:#4d4e53;
  --box-padding: 0 10px;

  /* 10px используется, потому что --box-margin не определен */
  margin: var(--box-margin, 10px);
}

Как вы наверное догадались вы можете использовать одни переменные для инициализации других:

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

Операции +, -, *, /

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

: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() приходит к нам на помощь, потому что без него такой подход работать не будет:

:root{
  --spacer: 10;
}

.box{
  padding: var(--spacer)px 0; /* не работает */
  padding: calc(var(--spacer)*1px) 0; /* работает */
}

Область видимости и наследование

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

Мы знаем, например, что в JavaScript переменные var ограничены областью действия функции.
У нас похожая ситуация с let и const с той лишь разницей, что у них блочная область видимости.

Замыкание в JavaScript это функция, которая имеет доступ к значениям переменных, вне этой функции – замкнутый контекст. Замыкание имеет доступ к замкнутым контекстам:

  • собственный контекст (например переменные объявленные внутри функции)
  • к переменным, объявленным вне функции
  • к глобальным переменным

История с препроцессорами выглядит похожим образом. Давайте возьмем SASS для примера, потому он выглядит наиболее популярным препроцессором на сегодняшний день.

С Sass мы имеем два типа переменных: глобальные и локальные.

Глобальная переменная может быть определена  за пределами любого селектора или конструкции(например миксина). В противном случае эта переменная будет трактоваться как локальная.

Любой вложенный блок будет иметь доступ к переменным, которые ему предшествуют.

Замыкания в JavaScript
Замыкания в JavaScript

Это означает, что в Sass область видимости переменных напрямую зависит от структуры кода.

В то время как в CSS пользовательские свойства по умолчанию наследуются и как и другие свойства каскадны.

Вы также не можете объявить глобальную CSS переменную вне селектора. Это не валидный подход в CSS. Глобальная область видимости для CSS это :root и только после этого переменная станет доступна глобально:

Давайте закрепим наши знания и применим пример Sass к HTML и CSS. Мы сделаем небольшое демо, использую нативный CSS. Сначала опишем наш HTML:

global
<div class="enclosing">
  enclosing
  <div class="closure">
    closure
  </div>
</div>

А тут идет наш CSS:

:root {
  --globalVar: 10px;
}

.enclosing {
  --enclosingVar: 20px;
}

.enclosing .closure {
  --closureVar: 30px;

  font-size: calc(var(--closureVar) + var(--enclosingVar) + var(--globalVar));
  /* 60px в данном случае */
}

See the Pen ybaaJJ by Denis (@denis-rudov) on CodePen.0

Изменения, примененные к переменным в CSS тут же применяются ко всем экземплярам.

До сих пор мы все еще не заметили какую либо разницу между переменными в CSS и в SASS. Однако давайте присвоим другое значение нашим переменным:

.closure {
  $closureVar: 30px; // локальная переменная
  font-size: $closureVar +$enclosingVar+ $globalVar;
  // 60px, $closureVar: 30px использовано

  $closureVar: 50px; // локальная переменная
}

Что касается SASS то это никак не отразится на размере шрифта. Но если такое же присвоение сделать в CSS, то это тут же вызовет пересчет предыдущего значения:

.enclosing .closure {
  --closureVar: 30px;

  font-size: calc(var(--closureVar) + var(--enclosingVar) + var(--globalVar));
  /* 80px тут, --closureVar: 50px используется */

  --closureVar: 50px;
}

Это первое большое различие: если вы заново присвоите значение пользовательским свойствам в CSS, то это вызовет пересчет всех пользовательских значений и вызовов calc() функций.

Препроцессоры ничего не знают о структуре DOM

Предположим мы бы хотели сделать размер шрифта font-size по умолчанию для блоков, за исключением тех, у которых присутствует класс highlighted:

<div class="default">
  default
</div>

<div class="default highlighted">
  default highlighted
</div>

Давайте применим к этому сниппету стили:

.highlighted {
  --highlighted-size: 30px;
}

.default {
  --default-size: 10px;

  /* Размер шрифта по умолчанию, кроме тех (.highlighted) где он переопределен */
  font-size: var(--highlighted-size, var(--default-size));
}

По причине того, что второй HTML  элемент с .default классом расширен .highlighted классом, то и свойства из highlighted класса будут применены к этому элементу.

В этом случае это означает, что --highlighted-size: 30px; будет применено, которое в свою очередь присвоит свойству  font-size значение highlighted-size.

Все очень просто и работает как и должно работать:

See the Pen KmggEm by Denis (@denis-rudov) on CodePen.0

Давайте попробуем добиться того же самого, но с помощью Sass:

.highlighted {
  $highlighted-size: 30px;
}

.default {
  $default-size: 10px;

  /* используем default-size, за исключением когда highlighted-size определен. */
  @if variable-exists(highlighted-size) {
    font-size: $highlighted-size;
  }
  @else {
    font-size: $default-size;
  }
}

See the Pen MmjbjG by Denis (@denis-rudov) on CodePen.0

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

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

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

И так запомните: пользовательские свойства CSS зависят от структуры DOM и являются динамическими.

Зарезервированные слова в CSS и использование свойства all

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

  • inherit – это ключевое слово означает, что будет использовано значение свойства родительского селектора
  • initial – применяет начальное значение для свойство, которое определено CSS спецификацией
  • unset – устанавливает наследуемое значение, если свойство наследуется стандартно(так и в случае пользовательских свойств) или начальное значение, если свойство наследуется не стандартно
  • revert – устанавливает свойство в дефолтное значение согласно стилям юзер-агента (пустое значение в случае пользовательских свойств)

Вот небольшой пример:

.common-values{
  --border: inherit;
  --bgcolor: initial;
  --padding: unset;
  --animation: revert;
}

Давайте рассмотрим другой случай. Предположим мы хотим создать компоненту и хотим быть уверены, что другие стили или пользовательские свойства не были к ней применены ненароком(обычно, в данном случае,  модульный подход CSS будет использован)

Но сейчас существует другой подход. Использование  свойства CSS all. Этот быстрый вариант сброса всех свойств CSS:

.my-wonderful-clean-component{
  all: initial;
}

К сожалению свойство all не сбрасывает пользовательские свойства CSS. По этому поводу развернулась целая дискуссия о добавлении дополнительного свойства — для сброса пользовательских значений. Так что в будущем сброс всех свойств может выглядеть так:

.my-wonderful-clean-component{
  --: initial; /*  сброс всех пользовательских CSS свойств */
  all: initial; /* сброс всех остальных CSS стилей */
}

Использование пользовательских свойств

Рассмотрим парочку примеров использования пользовательских свойств в CSS.

Эмуляция несуществующих правил CSS

Имя этим переменных “пользовательские свойства”. Так почему же не использовать их для эмуляции несуществующих свойств?

А таких хватает. Например translateX/Y/Zbackground-repeat-x/y (все еще нет поддержки для всех браузеров), box-shadow-color.

Давайте заставим последнее свойство заработать. Давайте в нашем примере заставим цвет тени измениться при наведении. Мы лишь хотим следовать DRY принципу(не повторяй сам себя), так что вместо того, чтобы повторять свойство box-shadow мы лишь изменим его цвет в секции псевдо-свойства :hover

.test {
  --box-shadow-color: yellow;
  box-shadow: 0 0 30px var(--box-shadow-color);
}

.test:hover {
  --box-shadow-color: orange;
  /* Instead of: box-shadow: 0 0 30px orange; */
}

Темы

Одной из самых важных возможностей является пользовательских свойств является темизация приложений. Пользовательские свойства были созданы как раз для решения такого рода проблем. Давайте сделаем простую тему для нашего компонента (тот же подход можно применить и для всего приложения)

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

А теперь предположим, что мы хотим сделать реверсию для наших цветов.

Первым шагом было бы вынесение всех значений цветов в пользовательские переменные. Давайте так и сделаем и перепишем нашу компоненту.

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

Это все, что нам нужно. Используя этот подход мы можем переписать значения наших переменных на инверсные значения и применить их, когда нам это захочется. Мы могли бы, например, добавить глобальный inverted класс для body и поменять цветовую схему нашей компоненты. Или всего приложения:

body.inverted .btn{
  --shadow-color: #888888;
  --gradient-from-color: #CB6724;
  --gradient-to-color: #D67F46;
  --color: #000000;
}

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

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

Category :

Leave a Reply

Your email address will not be published. Required fields are marked *