Сравнение CSS-переменных на чистом CSS
Сравнение CSS-переменных в чистом CSS — это значительный прорыв. Давайте убедимся, что это действительно возможно.
Перед вами три элемента списка, в которых с помощью инлайновых стилей определены переменные --x
и --y
с разными значениями.
Видеоверсия этого курса доступна во ВКонтакте и на Ютюбе
Подключив стилевой файл compare.css
, мы видим, что элементы списка стилизуются по-разному в зависимости от значений переменных. Если у элемента значение --x
меньше, чем --y
, фон будет красным. Если --x
больше --y
, фон станет зелёным. Когда значения равны, фон будет серым.
Важно! На данный момент эта возможность доступна только в браузере Chrome, но до её полноценной поддержки в других браузерах остался всего один маленький шаг.
Вы можете самостоятельно изменять значения переменных и убедиться, что это действительно диапазонные сравнения, а не статичные значения, зафиксированные в подключённом стилевом файле.
Но ещё важнее вопрос: будут ли эти сравнения работать динамически? Например, при наведении на элементы?
Чтобы проверить это, уберём инлайновые стили.
И перенесём переменные в обычный стилевой файл. Всё работает так же, как и с инлайновыми стилями.
Теперь для каждого элемента изменим значения переменных при наведении, чтобы результат сравнения менялся.
Наведите курсор на элементы по очереди — и вы увидите, как меняются их стили. Это идеально! Мы можем использовать всю мощь нативных технологий и применять сравнения в реальном времени, например, при типовых взаимодействиях.
Давайте разберём по шагам, как работает эта магия.
Для начала отключим файл со стилями, чтобы увидеть изменения. Оставим один элемент и зададим ему переменные с разными значениями.
Важный подготовительный шаг.
Зарегистрируем переменную --sign
с помощью директивы @property
.
А вот и ключевой ингредиент приёма — sign(var(--x) - var(--y))
.
Всего одна строчка кода делает возможными диапазонные сравнения переменных.
Мы вычитаем одну переменную из другой и получаем знак результата. Если переменная --x
больше --y
, то результат вычитания будет положительным, и sign()
вернёт 1
; если меньше, то получим -1
; если переменные равны, то получим 0
.
А дальше дело техники. Используем ограниченные возможности CSS-запросов для сравнения переменных, которые могут принимать три значения: -1
, 1
и 0
.
Сначала реализуем это с помощью новой функции if()
. Добавим условие для значения -1
и покрасим фон в красный.
Вернём элементы списка в разметку, чтобы протестировать другие случаи.
Зададим для новых элементов другие значения переменных. Во втором элементе переменные равны, а в третьем --x
больше --y
.
Добавим в if()
ещё одну ветку для значения 1
, когда --x
больше --y
. Последний элемент окрашивается в зелёный цвет. Всё работает.
Для случая с равенством используем else
. Второй элемент стал серым.
Переменная --sign
доступна и внутри псевдоэлемента, поэтому для наглядности мы можем добавить подписи с результатом сравнения. Также используем if()
.
Вот и весь приём. Вместо компактных и привычных операторов сравнения нам приходится использовать несколько строк кода. Но всё работает.
Теперь немного хардкора. Продолжайте смотреть демонстрацию, если вам интересно погрузиться в тонкости работы этого приёма.
Давайте разберёмся, зачем нужна регистрация переменной --sign
. Закомментируем директиву @property
. Проверки перестали работать.
Зарегистрированные и незарегистрированные (обычные) CSS-переменные работают по-разному.
Обычные переменные наследуются в виде исходной строки, а не вычисляются. Поэтому значение обычной переменной, которое видит функция if()
, будет выглядеть так: sign(var(--x) - var(--y))
. Если подставить это значение в условие, первая ветка сравнения сработает для всех элементов.
Если снова раскомментировать директиву @property
, так что --sign
станет зарегистрированной переменной, то увидим интересную картину: фон блоков остался красным, а подписи исправились.
Оказывается, выражения внутри условий в if()
тоже вычисляются. Поэтому у всех трёх блоков выражение sign(var(--x) - var(--y))
вычисляется дважды (при создании переменной и в условном операторе), и даёт одинаковый результат. В результате всегда срабатывает условие в первой ветке.
Если переместить sign(var(--x) - var(--y))
во вторую ветку, то результат изменится.
Функция if()
всегда возвращает значение для первой успешной проверки. Первая проверка в нашем случае — это проверка на -1
. Там, где она срабатывает, фон будет красным.
Во всех остальных случаях всегда будет срабатывать вторая проверка. Вот такие тонкости.
Решение с if()
не является единственно возможным.
Ключевая технология подхода — это контейнерные стилевые запросы. Поэтому можно переписать решение с использованием директивы @container
. Поддержка @container
значительно лучше, чем у if()
, и работа над релизом этой функции в Firefox уже активно ведется.
При использовании @container
нам понадобится дополнительная обёртка или вложенный элемент, стили которого мы будем менять. В целом, ничего страшного — опытного верстальщика дополнительной обёрткой не испугать.
В CSS оставим регистрацию переменной --sign
.
Сравниваемые переменные зададим для родительского блока — .comparison
. Там же вычислим значение для --sign
.
Стили, которые зависят от результата сравнений, пропишем для вложенного блока .comparison-result
с помощью обычного контейнерного стилевого запроса.
Все стили для состояния «меньше» определим внутри контейнерного запроса.
@container style(--sign: -1) { ... }
Стили для состояний «равно» и «больше» зададим внутри контейнерных запросов:
@container style(--sign: 0) { ... }
@container style(--sign: 1) { ... }
Добавим в разметку дополнительные элементы, зададим им разные значения переменных и убедимся, что решение с директивой @container
работает так же, как решение с if()
.
Решение с if()
компактнее решения с @container
, когда нужно менять только одно свойство.
Но когда необходимо изменить много свойств в нескольких CSS-правилах, лучше использовать @container
.
Напоследок изменим значения переменных второго элемента при наведении. Решение с @container
работает и в этом случае.
Берите этот приём на вооружение, так как уже очень скоро его можно будет использовать в продакшене.