CSS-переменные: о чем вам не говорят

0
536
views

Перевод статьи «What no one told you about CSS Variables».

Photo by David Clode on Unsplash

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

1. Будьте осторожны с !important

Использование !important с CSS-переменными имеет свои особенности. Давайте начнем с базового примера:

p {
  --color:red!important;

  color:var(--color);
  color:blue;
}

Каким будет цвет p? Думаете, красный, потому что код будет таким:

p {
  color:red!important;
  color:blue;
}

Ничего подобного! Цвет p будет синий, потому что код будет следующим:

p {
  color:red;
  color:blue;
}

В данном случае !important не является частью значения цвета. Он используется для повышения специфичности --color. Из спецификации:

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

Вот еще один пример для лучшего понимания:

p{
  --color:red!important;
  --color:blue; 

  color:var(--color);
}

Этот код даст нам красный цвет:

  1. У нас есть два объявления одного свойства --color, так что нам нужно разрешить каскад. В первом из объявлений есть !important, а значит, оно побеждает.
  2. У нас есть победитель (--color:red!important), поэтому !important удаляется, а значение применяется к color.
  3. Мы получаем color:red.

Давайте изменим наш код:

p{
  --color:red!important;
  --color:blue; 

  color:var(--color);
  color:blue;
}

Следуя той же логике, мы разрешаем каскад для --color и для color. --color:red!important — победитель, так же, как и color:blue, так что в конце мы имеем blue, потому что color:var(--color) нас больше не волнует.

Правило important всегда расценивает CSS-переменные (пользовательские свойства) как обычные свойства, а не как переменные, хранящие значения.

Пользовательские свойства — это обычные свойства, так что они могут объявляться для любого элемента и разрешаться согласно обычных правил наследования и каскада. Их можно делать условными при помощи @media и других условных правил. Они могут использоваться в стилях атрибута в HTML, читаться или устанавливаться при помощи CSSOM и т. д.

2. CSS-переменные не могут содержать url

С этим ограничением вы будете сталкиваться довольно часто.

Так делать нельзя:

:root {
  --url:"https://picsum.photos/id/1/200/300";
}
.box {
  background:url(var(--url));
} 

Нужно делать так:

:root {
  --url:url("https://picsum.photos/id/1/200/300");
}
.box {
  background:var(--url);
} 

Это ограничение связано с тем, как парсится url(). Объяснить это довольно сложно, но, как видим, исправить довольно легко. Всегда добавляйте часть url() внутри CSS-переменной.

Если хотите больше подробностей, почитайте этот ответ на Stack Overflow.

3. Они могут сделать невалидное значение валидным!

Это одна из моих любимых особенностей CSS-переменных. Но она же способна обеспечть вам головную боль.

Начнем с простого примера:

.box {
  background: red;
  background: linaer-gradient(red, blue);
}

Наш .box будет иметь градиентную раскраску… хотя нет, погодите, у него почему-то красный фон. Ага! Я допустил опечатку в linear-*. Мне легко заметить свою ошибку, потому что браузер перечеркнул это объявление и использовал предыдущее.

Теперь давайте введем переменную:

.box {
  --color:red;
  background: var(--color);
  background: linaer-gradient(var(--color), blue);
}

Протестируйте код и вы увидите, что фон стал прозрачным, а наше второе объявление больше не перечеркнуто, потому что теперь оно валидно. Более того, теперь перечеркнуто первое объявление, потому что второе его перезаписывает.

Какого лешего?..

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

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

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

.box {
  --color:10px; /* "валидная" переменная */
  background: red; /* "валидное" объявление */
  background:linear-gradient(var(--color),blue); /* "валидное" объявление, которое перезапишет первое  */
  /* Результат - "невалидное" значение ... */ 
}

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

4. CSS-переменные могут использоваться без единиц измерения

Практически во всех руководствах и курсах вам покажут следующий пример:

:root {
 --p: 10px;
}
.box {
  padding: var(--p);
}

Но вы можете сделать и так:

:root {
 --p: 10;
}
.box {
  padding: calc(var(--p)*1px);
}

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

Вот один из многих примеров (источник — ответ на Stack Overflow):

See the Pen Cube CSS by Temani Afif (@t_afif) on CodePen.

Это важная особенность, не забудьте о ней. Однажды она спасет ваш день.

5. Их можно анимировать

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

Animatable: no

Но теперь все изменилось, и благодаря появлению @property мы можем делать анимации и переходы с CSS-переменными.

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

Я писал об этой фиче здесь и здесь.

6. CSS-переменные не могут хранить значение inherit

Давайте рассмотрим пример:

<div class="box">
  <div class="item"></div>
</div>
.box {
  border:2px solid red;
}
.item {
  --b:inherit;
  border:var(--b);
}

Интуитивно может показаться, что .item унаследует border родительского элемента, потому что --b содержит inherit, но на самом деле этого не произойдет. Попробуйте и убедитесь сами.

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

Пример:

.box {
  --b:5px solid blue; /* we define the variable on the parent */
}
.item {
  --b:inherit; /* the child will inherit the same value so "5px solid blue"*/
  border:var(--b); /* we will have "5px solid blue" */
}

Как видите, логика наследования применяется к ним так же, как к обычным свойствам.

Стоит отметить, что делать так, как показано выше, — бесполезно, потому что CSS-переменные наследуемы по умолчанию. Это все равно что устанавливать inherit для любого другого свойства, которое наследуется по умолчанию (например, color).

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

Та же логика применима к другим ключевым словам, например unset и revert (How to set CSS variable to the value unset, “—unset-it: unset”?).

7. Они могут быть пустыми

Вы можете сделать следующее:

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

Согласно спецификации, это допустимо:

Примечание. Хотя <declaration-value> должно представлять как минимум один символ, этот символ может быть пробелом. Это предполагает, что свойство --foo: ; валидно, и соответствующий вызов var(--foo) будет содержать простой пробел в замещающем значении. Но --foo:; невалидно.

Обратите внимание на последнее предложение. Нам нужен как минимум один пробел. Следующий код невалидный:

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

Этот прием, используемый с fallback, помогает творить чудеса.

Простой пример для демонстрации этого трюка:

.box {
  background:
   linear-gradient(blue,transparent)
   var(--color,red); 
}
<div class="box">
  I will have `background:linear-gradient(blue,transparent) red;`
</div>
<div class="box" style="--color:green">
  I will have `background:linear-gradient(blue,transparent) green;`
</div>
<div class="box" style="--color: ;">
  I will have `background:linear-gradient(blue,transparent)  ;`
</div>
  1. В первом box нет определенных переменных, поэтому будет использоваться fallback.
  2. Во втором есть определенная переменная, так что будет использоваться она.
  3. В последнем определена пустая переменная, поэтому будет использоваться эта пустота. Это как будто у нас больше нет var(--color,red).

Пустое значение позволяет нам удалить объявление var() из свойства! Это может быть полезно при использовании var() внутри сложного значения.

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

8. CSS-переменные это не переменные C++

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

Что все пытаются делать:

:root {
  --p:5px;
  --p:calc(var(--p) + 1px); /* давайте инкрементируем на 1px */
}
:root {
  --x: 5px;
  --y: 10px;
  /* давайте сделаем switch переменных */
  --c: var(--y);
  --y: var(--x);
  --x: var(--c);
}
.box {
  --s: 10px;
  margin:var(--s); /* Я хочу margin 10px */
  --s: 20px;
  padding:var(--s): /* а затем padding 20px */
}

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

В последнем примере и padding, и margin будут иметь 20px, потому что каскад отдаст приоритет последнему объявлению --s: 20px, которое применится и к margin, и к padding.

В общем, прекратите думать о C++, JavaScript, Java и т. д., работая с CSS-переменными, потому что они — пользовательские свойства, обладающие собственной логикой.

9. Они переходят только от родителя к потомку

Запомните золотое правило: CSS-переменные всегда распространяются от родительского элемента (или предка) к элементам-потомкам. Они никогда не переходят от потомка к родителю или к другим элементам одного уровня (сиблингам).

Это приводит нас к следующей ошибке:

:root {
  --c1: red;
  --c2: blue;
  --grad: linear-gradient(var(--c1),var(--c2);
}
.box {
  --c1: green;
  background:var(--grad);
}

Вы думаете, что фон .box будет linear-gradient(green, blue)? Нет, будет linear-gradient(red, blue).

Элемент root — самый верхний в DOM, поэтому он — предок нашего элемента box. Золотое правило гласит, что свойство может переходить только от родителя к потомку, поэтому —c1 не может перейти в противоположном направлении, чтобы достигнуть элемента root, изменить --grad, а затем вернуться в обратном направлении, чтобы заново послать измененное значение --grad.

В этом примере .box будет наследовать значение --grad со значениями --c1 и --c2, определенными в root. Изменение --c1 лишь изменит значение --c1 внутри .box, не больше.

Этот финт сбил с толку даже команду Stack Overflow.

10. У CSS-переменных может быть странный синтаксис

Последняя (и забавная) особенность.

Вы знали, что можно делать вот так?

body {
  --:red;
  background:var(--);
}

Потрясающе, правда? Да, вы можете определять CSS-переменные, используя только два дефиса.

Если вы думаете, что это сумасшествие, взгляните сюда:

body {
 --📕:red;
 --📗:green; 
 --📘:blue;
 --📙:orange;
}

Ага, эмодзи! Вы можете определять переменные, используя эмодзи, и они будут работать.

Синтаксис CSS-переменных позволяет практически что угодно. Единственное требование — начинать с --. Также можно начинать с цифры (например --1:).

Почему бы не использовать одни дефисы?

body {
  ---------:red;
  background:var(---------);
}

Или вот одна переменная, хранящая два разных значения:

body {
  --‎​:red;
  --‎:blue;
  background:linear-gradient(90deg, var(--‎​),var(--‎));
}

Запустите этот код, и вы получите градиентную раскраску!

Для этого фокуса я использовал скрытый символ, благодаря которому переменные стали разными, хотя визуально кажутся одинаковыми. Открыв этот код на jsfiddle.net, вы увидите следующее:

Разумеется, это ни в коем случае нельзя использовать в реальном проекте. Ну разве что вам захочется свести с ума босса и коллег.

Итоги

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

verstka logo

Хочешь знать больше про веб?

Подпишись на наш телеграм-канал TechRocks WEB-разработка?

×

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here