Генератор цветовых тем

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

Идея генератора возникла когда я начала делать другой свой проект — Grid Сheatsheet. У меня уже был очень похожий справочник, Flex Сheatsheet, и изначально я собиралась просто поменять данные и легко и непринуждённо получить шпаргалку по гридам, но всё оказалось сложнее: у гридов немного другая структура спецификации, которая не ложилась в существующую структуру справочника, да и JavaScript я теперь знаю немного получше, поэтому движок для справочников в итоге пришлось переписать полностью. Но вернёмся к цветовым темам и посмотрим с чего всё началось.

В день запуска шпаргалки по флексам самым популярным вопросом стало «А что с цветами?» На тот момент страница справочника выглядела вот так:

Flex cheat sheet в момент старта

Я подобрала палитру на свой вкус и мне было отлично, но желающих сделать что-то с цветами оказалось достаточно много, поэтому я по-быстрому прикрутила более нейтральную тему:

Flex cheat sheet с серой темой

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

Для проекта, который один раз сделали и забыли, это не проблема, но в процессе активной разработки поддерживать в актуальном состоянии файл с темой становилось проблематично. Хотелось сохранить цветную тему, но при этом как-то обойтись без дублирования стилей.

Препроцессорные переменные не помогают решить эту задачу, потому что их нельзя использовать в одном месте, а потом переопределить по классу и получить в другом месте CSS-свойство с новым значением:

Чтобы второй квадратик стал жёлтым, придется продублировать всё свойство, где используется переменная.

Но есть кастомные свойства, которые работают как нужно:

Сначала объявляем переменную --color и используем её в background. Если потом внутри класса с модификатором задать ей другое значение (например, --color: gold;), оно поменяется и в объявлении фона, но только для элемента с таким классом, при этом строчку с объявлением фона не нужно дублировать — просто переопределяем переменную и всё волшебным образом работает.

Кастомные свойства не поддерживаются в IE11, но посетителей с IE на интересующих меня ресурсах крайне мало (меньше 1%), так что я подумала, что уже вполне могу использовать CSS-переменные в своих проектах.

Делать темы с ними, конечно, гораздо удобнее. Например, есть такая карточка:

И вот такой набор цветов для неё:

:root {
  --body-bg: whitesmoke
  --card-bg: #FFF;
  --border: #CCC;
  --shadow: rgba(0,0,0,.1);
  --title: teal;
  --text: #333;
  --link: mediumseagreen;
  --link-hover: turquoise;
}

Если потом потребуется добавить такую же карточку но с другими цветами, для этого не придется дублировать всю разметку, достаточно переопределить нужные переменные внутри класса:

:root {
  /* Цвета по умолчанию */
  --body-bg: whitesmoke
  --card-bg: #FFF;
  --border: #CCC;
  --shadow: rgba(0,0,0,.1);
  --title: teal;
  --text: #333;
  --link: mediumseagreen;
  --link-hover: turquoise;
}

.widget--red {
  /* Цвета для красной карточки */
  --card-bg: mistyrose;
  --border: tomato;
  --shadow: rgba(200,0,0,.2);
  --title: orangered;
  --link: tomato;
  --link-hover: crimson;
}

Также, в отличие от препроцессорных переменных, кастомные свойства видны в веб-инспекторе:

Кастомные свойства в веб-инспекторе

И их подсказывает браузер:

Браузер подсказывает кастомные свойства

Можно просто листать переменные вверх-вниз и сразу видеть изменения на странице. Это невероятно удобно.

С кастомными свойствами работать с темами стало проще, но чем больше цветов я выносила в переменные, тем сложнее становилось подбирать оттенки.

В изначальной палитре 5 цветов, в оформлении справочника требуется около сорока цветовых переменных, где брать оттенки? Конечно, какие-то цвета можно использовать несколько раз, но всё равно их не хватает.

Ещё часто нужен оттенок чуть светлее, или чуть темнее (например, ссылка немного меняет цвет при наведении), сделать это в паре мест не проблема, но если таких вариаций десяток? С каким шагом будет меняться яркость? Шаг общий для всех или в каждом случае свой? Значит ещё надо как-то унифицировать шаги изменения цвета.

Или вот другая проблема: у справочника нет фиксированного дизайна, значит какие-то оттенки нужно подбирать в процессе вёрстки. Как удобно «ходить» по цветам? Например, взять оттенок, потом попробовать чуть более темную версию или более светлую, но при этом чтобы можно было легко перейти к любой из них?

Где-то в этом месте мне пришла в голову мысль, что хорошо бы палитра с вариациями оттенков светлее/темнее собиралась сама по себе — чтобы сразу были оттенки, по которым можно ходить, без необходимости подбирать их руками.

Для манипуляций с цветом удобно использовать формат HSL (Hue-Saturation-Lightness). Главные преимущества формата — читаемость и возможность поменять значение руками. Конечно, редактировать руками можно цвета, записанные в любом формате, но только в HSL это понятный и управляемый процесс (поэтому это мой любимый формат). Запись цвета в HSL выглядит вот так:

hsl(0, 100%, 35%)

Первое значение — позиция на цветовом круге в градусах (от 0 до 360). Второе — насыщенность, третье — яркость. Второе и третье значения задаются в процентах, от 0 до 100%.

Если записать цвет в HSL, а потом менять яркость (третий параметр), из основного цвета можно получить дополнительные оттенки. Например:

Из трёх изначальных цветов легко получается ещё шесть. Такую палитру можно просто написать руками.

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

/* Тёмные оттенки */
--color-darkest: hsl(...);
--color-darker: hsl(...);
--color-dark: hsl(...);
/* Базовый цвет */
--color: hsl(...);
/* Светлые оттенки */
--color-light: hsl(...);
--color-lighter: hsl(...);
--color-lightest: hsl(...);

Чуть светлее — light, ещё светлее — lighter, самый светлый — lightest. С тёмными аналогично. Итого семь оттенков для каждого цвета: один базовый и по три шага в каждую сторону.

Чтобы автоматизировать процесс получения палитры, я сделала Tema. В поле ввода вставляются любые переменные с цветами в любом формате, включая именованные, и инструмент отдаёт расширенный набор цветовых переменных:

Tema, поля ввода и вывода

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

Можно настроить шаг изменения яркости, количество шагов и выбрать формат цветов в итоговом наборе:

Tema, настройки

Полученная палитра копируется в файлы со стилями и можно начинать пользоваться.

Правда, при такой системе именования цвета имеют абстрактные имена и уже не привязаны к месту использования. Это решается дополнительными переменными, которые связывают одно с другим:

:root {
  /* Палитра */
  --color-1-darkest: #f7cd22;
  --color-1-darker: #f9d958;
  --color-1-dark: #fbe58f;
  /* ...  */
  --color-5-light: #988b78;
  --color-5-lighter: #b0a797;
  --color-5-lightest: #c8c2b7;

  /* Привязка цветов к переменным для стилизации интерфейса */
  --body-bg: var(--color-3-lighter);
  --card-bg: var(--color-1-light);
  --border: var(--color-3);
  --shadow: rgba(0,0,0,.1);
  --title: var(--color-4-darker);
  --text: #333;
  --link: var(--color-4-darker);
  --link-hover: var(--color-4);
}

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

Также разделение хранения и использования цветов позволяет легко добавлять на сайт новые цветовые темы. Палитры добавляются в стили, в качестве селектора, который будет ограничивать их действие, удобно использовать data-атрибут (ему потом будет проще перезаписать значение через JS):

[data-theme="red"] {
  --color-1-darkest: hsl(48, 93%, 61%);
  /* ... */
}

[data-theme="blue"] {
  --color-1-darkest: hsl(48, 93%, 61%);
  /* ... */
}

Затем атрибут data-theme добавляется на элемент <html> и его значения меняются по клику на переключатель темы:

При изменении атрибута CSS-переменные, заданные для него в стилях, перезаписывают значения исходных переменных, и страница перекрашивается. Никакого дублирования стилей или перекрашивания переменных вручную — один набор переменных просто переопределяет другой. Работает как волшебство.

Если в какой-то из тем один из оттенков оказался не совсем подходящим, ниже под палитрой темы можно переопределить значение переменной для стилизации:

[data-theme="red"] {
  --color-1-darkest: hsl(48, 93%, 61%);
  /* ... */

  --border: var(--color-3-light);
}

Например, в предыдущем демо в красной теме третий цвет (--color-3), используемый для рамки, оказался чуть темнее, чем хотелось бы, поэтому внутри темы я задала для --border более светлый оттенок (--color-3-light).

Именно этот подход позволил мне не только сохранить и поддерживать дополнительную тему, но и добавить 4 новых. Я обычно использую эту:

Tema, настройки

Но есть и более спокойные варианты : )

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

При создании тем с помощью генератора нужно иметь в виду пару нюансов:

  1. Количество цветов в наборах должно совпадать. Например, если в одной из цветовых тем используется var(--color-1-lightest), самый светлый оттенок самого светлого цвета, он должен быть во всех наборах цветов. Но при генерации палитры рассчитанное значение яркости может выйти за границы разумного, и тогда этих цветов в наборе не будет: Tema, битые цвета Нужно помнить, что так бывает, и обязательно проверять все ли цвета на месте.
  2. Чтобы палитры были взаимозаменяемыми, порядок цветов в них должен совпадать. Например, во всех идти от самого светлого цвета к самому тёмному. Тогда для добавления новой темы будет достаточно положить новый набор переменных в CSS.
Ссылки по теме:
CSS Custom Properties for Cascading Variables
Tema
Цвета в CSS
Handy colorsименованные цвета с палитрами
Jekyll → GatsbyЕдиницы размеров в CSS
Наверх