Вы знаете, что можно динамически менять цветовое оформление страницы без JavaScript или CSS-препроцессоров? Это возможно благодаря магии кастомных свойств CSS, цветовой модели HSL и функции calc()
. Они работают с любым фреймворком и веб-технологией без лишних телодвижений. Да здравствует CSS!
- Что нам дают CSS-препроцессоры
- Настраиваем окружение
- Корректируем значения с помощью HSL
- Получаем комплементарные цвета и триадические сочетания
- Воспроизводим mix()
- Создаём функцию color-contrast()
- Заключение
В демо на CodePen можно выбрать основной и дополнительный цвета и создать завершённую систему. Это реализовано на ванильном CSS. JavaScript здесь используется только для динамического изменения цветов.
Что нам дают CSS-препроцессоры
Когда заходит речь о системах цветов, на ум приходят несколько полезных функций Sass. Вот наиболее распространённые:
-
lighten
,darken()
; -
complement()
; -
hue()
; -
mix()
; -
contrast-color()
.
Некоторые из этих трансформаций можно воссоздать с помощью CSS-фильтров. Например, lighten
и darken
— это всего лишь значение lightness
из HSL. Трансформация hue
— это H из HSL. Дополнительные цвета можно рассчитывать с помощью инверсии hue
, для этого достаточно прибавить к или отнять 180 от соответствующего значения. Это переносит значение hue
на противоположную сторону цветового круга. Функция calc()
и кастомные свойства позволяют трансформировать цвета с помощью одного значения.
Ниже рассказывается, как с помощью кастомных свойств CSS и функции calc()
воссоздавать lighten()
darken()
, comlement()
и даже триадические сочетания. Также вы узнаете лайфхак, который позволяет делать крутые штуки с color-contrast()
. Эти возможности реализованы во всех современных браузерах. Но для Internet Explorer 11 и ниже придётся использовать препроцессор.
Настраиваем окружение
В первую очередь нужно разложить цвета на значения hue, saturation и lightness (тон, насыщенность и светлота) цветовой модели HSL. Можно также использовать значение альфа — прозрачность. Например, переменная для цвета red
выглядит так:
--colorPrimary: hsl(0, 100%, 50%);
Объявляем пользовательские свойства со значениями HSL.
--colorPrimary-h: 0;
--colorPrimary-s: 100%;
--colorPrimary-l: 50%;
--colorPrimary: var(--colorPrimary-h), var(--colorPrimary-s), --colorPrimary-l);
Корректируем значения с помощью HSL
Теперь значения из примера выше можно использовать для корректирования цветов. Начать стоит с воссоздания функций lighten
и darken
.
Определим, на сколько светлее или темнее должен стать цвет. Эти значения сохраним в дополнительных кастомных свойствах.
--lighten-percentage: 20%;
--darken-precentage: 15%;
После определения трансформаций можно записать новые значения. Показатель lightness можно вычислить так.
calc(var(--colorPrimary-l) + var(--lighten-percentage))
Всё вместе выглядит так.
--colorPrimary--light: var(--colorPrimary-h), var(--colorPrimary-s),
calc(var(--colorPrimary-l) + var(--lighten-percentage)));
Код может показаться сложным. Но здесь только используются базовые значения hue и saturation и уточняется значение lightness.
Читайте также: 7 необычных приёмов и инструментов HTML и CSS
Чтобы реализовать функцию darken
, можно вычесть значение lightness или прибавить его, если вам нужны отрицательные значения.
--colorPrimary--dark: var(--colorPrimary-h), var(--colorPrimary-s),
calc(var(--colorPrimary-l) - var(--darken-percentage)));
Получаем комплементарные цвета и триадические сочетания
Цветовой круг HSL
Чтобы получать комплиментарные или триадические оттенки, нужно уточнять значение hue базового цвета. Можно создать отдельные переменные или использовать шорткаты и помнить, что цветовой круг HSL изменяется по часовой стрелке от нуля до 360 градусов (см. иллюстрацию выше). То есть чтобы получить комплементарный оттенок, необходимо отнять или прибавить к значению 180 градусов.
Получаем комплементарный оттенок
--colorPrimary--complement: calc(var(--colorPrimary-h) + 180), var(--colorPrimary-s), var(--colorPrimary-l));
Чтобы получить триадическое сочетание, нужно разделить цветовой круг на три части. То есть оттенки можно получить, добавив к базовому значению 120 и 240 соответственно.
Триадические сочетания
--colorPrimary--triad1: calc(var(--colorPrimary-h) + 120), var(--colorPrimary-s), var(--colorPrimary-l));
--colorPrimary--triad2: calc(var(--colorPrimary-h) + 240), var(--colorPrimary-s), var(--colorPrimary-l));
Воспроизводим mix()
Режим смешивания mix()
более сложный по сравнению с lighten()
и darken()
. Но его можно воспроизвести с помощью HSL-вычислений.
Чтобы смешать цвета, надо определить их.
--color-1-h: 0;
--color-1-s: 100%;
--color-1-l: 50%;
--color-2-h: 50;
--color-2-s: 80%;
--color-2-l: 50%;
Затем надо получить средние значения.
--avg-1-2-h: calc((var(--color-1-h) + var(--color-2-h)) / 2);
--avg-1-2-s: calc((var(--color-1-s) + var(--color-2-s)) / 2);
--avg-1-2-l: calc((var(--color-1-l) + var(--color-2-l)) / 2);
Теперь можно получить смешанный цвет.
--mixed-color: hsl(var(--avg-1-2-h), var(--avg-1-2-s), var(--avg-1-2-l));
Создаём функцию color-contrast()
Ещё одна ключевая возможность CSS-препроцессоров — использование логических значений. Они позволяют вычислять доступные цвета с помощью функции color-contrast()
.
Эта функция принимает серию значений: базовый цвет, который выступает в качестве контрастного, значения light и dark. Функция возвращает самый контрастный цвет по отношению к базовому. Это невозможно воспроизвести на чистом CSS, поэтому понадобятся дополнительные хаки.
Наглядная демонстрация возможностей системы цветов
Сначала определяем порог контрастности.
--contrastThreshold: 60%;
Затем проверяем, не превышает ли значение lightness порог контрастности. Для этого используем демонстрационную версию системы цветов. Если порог выше яркости основного цвета, возвращается значение light, так как мы умножаем значение lightness на - 100. Например, значение lightness составляет 40, а contrastThreshhold
60. Разница равна - 20. Умножаем это значение на - 100 и получаем 2000. Это белый цвет, так как при значении lightness 100 и выше всегда получается белый.
Когда значение lightness выше contrastThreshhold
, получаем обратную ситуацию. Если lightness 90, а contrastThreshold
60, разница составляет 30. Умножаем это значение на - 100 и получаем -3000. Это чёрный цвет, так как lightness ниже нуля.
Возвращаем lightness как L в HSL. Это выглядит так.
.primary {
background: var(--colorPrimary);
--switch: calc((var(--colorPrimary-l) - var(--contrastThreshold)) * -100);
color: hsl(0, 0%, var(--switch));
}
Поскольку HSL — это то, как компьютер, а не пользователь понимает lightness, этот инструмент не идеальный. То есть у кастомных свойств и функции calc()
есть недостатки, как у любого другого инструмента.
Заключение
В статье рассмотрели удобные способы работы с кастомными свойствами и функцией calc()
. Если у вас есть вопросы или дополнения, пишите их в комментариях.
Адаптированный перевод статьи Calculating Color: Dynamic Color Theming with Pure CSS by Una Kravets.