Переменные в CSS (или custom properties, кому как удобнее) изначально задумывались для хранения повторяющихся свойств вроде цветовой палитры или шрифтов в одном месте. В препроцессорах работа с переменными куда более гибкая, но магия SASS/SCSS применима не всегда и не везде, и в реальном мире часто обходятся без них, что нередко ведёт к раздуванию и размазыванию кодовой базы по разным файлам и форматам. В этой статье мы рассмотрим несколько интересных хаков, которые позволяют построить на механизме custom properties вещи, кажущиеся невозможными без препроцессоров или вмешательства JS.
Избегаем повторного определения цветов
Определять темы в чистом CSS не самое приятное занятие: обычно переключение на тёмную палитру требует смены сразу многих цветов для многих элементов: фоны, текст, ссылки, кнопки и так далее. Исходные предпочтения пользователя получаются медиа-запросом prefers-color-scheme, внутри которого нужно расставить новые цвета для всех селекторов, что приводит к раздуванию:
:root {
--background: #fff;
--text-color: #000;
--link-color: #0089c7;
--primary-color: #165fb9;
/* ... */
}
@media (prefers-color-scheme: dark) {
:root {
--background: #1b1b1b;
--text-color: #eaeaea;
--link-color: #b76c10;
--primary-color: #8916b9;
/* и в том же духе на десятки строк в разных скоупах */
}
}
В CSS нет другого механизма для изменения переменных, но повторения всё же можно избежать с помощью дополнительных значений:
--background: var(--light, #fff) var(--dark, #1b1b1b);
Если использовать переменную --light
со значением initial, а в --dark
передать валидное, но неприменимое значение, то --background
получит цвет #fff. Для этой ситуации у CSS такое значение есть, и это… пробел. Таким образом, для белой темы строка распарсится так:
--background: #fff ;
А для тёмной так:
--background: #1b1b1b);
Обратите внимание на пробелы, они не ломают синтаксис (что сбросило бы определение всей строки). Теперь осталось только вынести переключатель состояния в отдельные переменные:
:root {
/* --ON и --OFF заменяют двоичную переменную */
--ON: initial;
--OFF: ;
}
/* выбираем светлую тему по умолчанию */
.theme-default,
.theme-light {
--light: var(--ON);
--dark: var(--OFF);
}
.theme-dark {
--light: var(--OFF);
--dark: var(--ON);
}
/* медиа-запрос теперь нужен только для переключения */
@media (prefers-color-scheme: dark) {
.theme-default {
--light: var(--OFF);
--dark: var(--ON);
}
}
Теперь цветовые схемы можно определять в одном месте, выглядеть это будет так:
:root {
--background: var(--light, #fff) var(--dark, #1b1b1b);
--text-color: var(--light, #000) var(--dark, #eaeaea);
--link-color: var(--light, #0089c7) var(--dark, #b76c10);
--primary-color: var(--light, #165fb9) var(--dark, #8916b9);
/* ... */
}
Такой код менее нагляден по сравнению с классическим определением, но к нему легко привыкнуть, и он не только экономит кучу места, но и уменьшает шанс ошибиться при изменении.
Используем switch-case в языке без логики
Как мы помним, в CSS не существует явных условных операторов для управления состоянием кроме медиа-запросов. Но структура этого языка иногда подбрасывает возможности там, где их никто не планировал. Встречайте: switch-case на анимации!
Для свойства animation
можно создавать любое количество ключевых кадров (), которые можно использовать как постоянное хранилище состояния, если держать анимацию остановленной. Для каждого кадра нужно знать точную задержку, чтобы остановленная анимация показывала нужный момент, а не фиксировалась на первом кадре. Вот наглядный пример:
Разберём принцип работы:
- Анимация остановлена через
animation-play-state: paused
. - Отрицательная задержка в
animation-delay
заставляет анимацию останавливаться на конкретном кадре (или между двумя определёнными кадрами, так работает градиент на первом ползунке). Значения на ползунке — от -100s до 0s. - В
animation-duration
можно указать любое удобное число, но нужно помнить, что при проигрывании последнего кадра анимация выключается, поэтому максимальная длительность не должна совпадать по времени с последним определённым кадром (case). Поэтому в примере выше разброс ползунка 100 секунд при длительности 100.001s.
Бинарная логика на функции calc()
В первом трюке мы уже использовали переменные --ON
--OFF
вместо двоичной переменной. В custom properties можно хранить числовые значения, и с помощью вычислений разных параметров через calc() и clamp() можно получать 0 или 1 в самых разных сценариях (подробнее в этой статье). Довольно неудобно даже инвертировать значение явным присваиванием, как в примере выше, а пытаться строить на этом какую-то логику и вовсе кошмар. Хорошо, что основные логические операции можно выполнять прямо в объявлении переменных!
not
Тут всё просто, 1 — 0 = 1, 1 — 1 = 0
--not: calc(1 - var(--j))
and
Простое умножение:
0 * 0 = 0
1 * 0 = 0
0 * 1 = 0
1 * 1 = 0
--and: calc(var(--k)*var(--i))
nand
1 — and = инвертированный and
--nand: calc(1 - var(--k)*var(--i))
or
Если хотя бы один из операндов равен единице, or возвращает единицу:
k or i = (not k) nand (not i)
--or: calc(1 - (1 - var(--k))*(1 - var(--i)))
nor
Аналогично с nand, nor = 1 — or:
--nor: calc((1 - var(--k))*(1 - var(--i)))
xor
Возвращает единицу, если ровно один из операндов равен единице:
--xor: calc((var(--k) - var(--i))*(var(--k) - var(--i)))
Заключение
Имея на руках бинарную логику и условные операторы, в CSS можно реализовать кучу вещей, которые раньше казалось возможным делать только через грубое вмешательство со стороны JS. Но есть один нюанс — чем глубже в дебри, тем менее читаемым становится код и тем сложнее его писать и поддерживать. Поэтому список подобных трюков можно продолжать ещё очень долго, но большинство приёмов из него почти наверняка не пригодятся в реальном мире. Зато основные концепции вроде switch и or помогут обойтись красивым CSS-only решением там, где раньше это казалось невозможным.
Облачные серверы от Маклауда подходят для размещения веб-проектов любой сложности! Вы можете создать собственную конфигурацию сервера в течение минуты и менять эти параметры в любой момент через удобную панель управления.
Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!
Автор: Mikhail