Сравнение 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 работает и в этом случае.
Берите этот приём на вооружение, так как уже очень скоро его можно будет использовать в продакшене.