Блоки и темы оформления.
Уже давно пытаюсь избавиться от боли модификатора _theme
у блоков.
У нас 2 темы блока button
- публичная и админка, около 2K строк стилей в сумме.
Имхо, блок должен быть максимально реиспользуемым и не тянуть ненужный код. Сейчас под тему запихиваются ВСЕ комбинации модификаторов блока, даже если они не нужны на странице. Это боль в поддержке.
Очень близки идеи http://whitepaper.tools/. В целом работаю тоже в этом направлении.
На данный момент пришёл к использованию 2-х видов переменных.
1. Глобальные/Контекстные (что реализовано в whitepaper)
Задаются через блок theme
и модификаторы theme_color_*
theme_size_*
и т.д.
Провайдят контекст переменных вниз по дереву.
.Theme_color_light {
--color-bg: #f7f7f7;
--coloron-bg: #222;
--color-primary: #222;
--coloron-primary: #fff;
--color-secondary: #777;
--coloron-secondary: #ddd;
--color-surface: #fff;
--coloron-surface: #222;
--color-error: #d00;
--coloron-error: #fff;
--color-success: #090;
--coloron-success: #fff;
}
2. Локальные (Уровень блока)
Задаются с указанием префикса имени блока. Такой способ явно ограничивает пространство имён переменных, чтобы случайно не сломать что-то внутри дерева. И позволяет легко изменять их через модификаторы.
Если спроецировать на мир React, то контекстные переменные - это пропсы, локальные - стейт :)
.Button {
--Button-bg: var(--color-bg);
--Button-border: var(--color-secondary);
--Button-color: var(--color-primary);
--Button-shadow: var(--color-bg);
--Button-fontSize: 1rem;
background: var(--Button-bg);
border: 1px solid var(--Button-bg);
color: var(--Button-color);
padding: 1em 2em;
font-size: var(--Button-fontSize);
transition: .3s;
border-radius: 3px;
}
.Button:hover {
background: var(--Button-color);
color: var(--Button-bg);
cursor: pointer;
box-shadow: 0 10px 20px var(--Button-shadow);
}
.Button_disabled,
.Button_disabled:hover {
background: #ddd;
border-color: #ddd;
color: #999;
cursor: default;
box-shadow: none;
}
Блок получает переменные из контекста и преобразует их в локальные для дальнейшего использования в своих стилях.
3. Модификаторы
Дальше через модификаторы жонглируем уже локальными переменными блока.
.Button_color_action {
--Button-bg: var(--color-action, #00a);
--Button-color: var(--coloron-action, #fff);
}
.Button_color_error {
--Button-bg: var(--color-error, #f00);
--Button-color: var(--coloron-error, #fff);
}
.Button_color_success {
--Button-bg: var(--color-success, #090);
--Button-color: var(--coloron-success, #fff);
}
Данный подход позволяет избавиться от длинных селекторов в css.
Например если мы не хотим использовать локальные переменные, то нужно писать классы таким образом
.Button_color_success {
background: var(--coloron-success, #fff);
color: var(--color-success, #090);
}
.Button_color_success.Button:hover {
background: var(--color-success, #090);
color: var(--coloron-success, #fff);
}
И это только 2 состояние, а там ещё N состояний, включая вложенные элементы...
Локальные переменные, конечно не серебренная пуля и каскады писать придётся. Но как решение вполне может помочь добиться лаконичных селекторов.
4. Именование цветов
После долгих ресёрчей толком не нашёл способа лаконично и предсказуемо описывать переменные. Наиболее лаконичным стилем нашёл подход из Material Design https://material.io/design/color/
В кратце, есть какой-то цвет и для него создаётся рекомендуемый контрастный цвет.
:root {
--color-primary: #000;
--coloron-primary: #fff;
}
В качестве дополнительного задания контраста есть идеи использовать доп. параметр -low
, -hight
:root {
--color-primary: #000;
--coloron-primary: #dddddd ; /* например для текста на фоне с цветом primary */
--coloron-primary-hight: #ffffff; /* сильноконтрастный цвет */
--coloron-primary-low: #777777; /* слабоконтрастный цвет */
}
В целом такое именование позволяет добиться предсказуемых результатов при создании темы.
Правда это тоже не является серебренной пулей, т.к. где-то могут не использоваться рекомендуемые переменные.
.Block {
background: var(--color-surface);
color: var(--color-error);
}
И не известно на сколько в новой теме surface
и error
будут контрастны друг к другу.
upd Пересмотрел ещё раз whitepapper https://github.com/whitepapertools/whitepaper-portal/tree/a6bb170522b470560e3572856cf11b1929e1469a/common.blocks/theme
Идёт разбитие цветовой темы на 2 вида. 1 базовые цвета страницы, 2 контролы (кнопки, чекбоксы, инпуты и т.д.).
Данный подход позволяет наиболее однозначно разрабатывать цвета для темы и тестировать их в "вакуме", что очень удобно. Тема не рассчитывается на все цвета радуги. Если нужно много вариаций, то стоит сделать несколько тем, что и плюс и минус.
Плюсы:
- Однозначность использования цветов
- Возможность разрабатывать тему "в вакуме"
Минусы:
- Нужно использовать несколько миксов темы на странице для нескольких комбинаций цветовых решений.
5. Старые браузеры
Любимый IE... Хоть поддержка по caniuse уже > 90% https://caniuse.com/#search=css-variables он ещё живёт...
Как один из подходов решения этого вопроса есть компромиссный вариант - делать версию с одной темой. Все переменные темы дублировать в :root
и через postcss ставить статичные значения.
Этот сегмент получит сильно урезанный вариант оформления... Если это сильно критично то от локальных переменных придётся отказаться и делать стилизацию через каскады.
Либо использовать подходы с css-in-js... Но они не лишены недостатков.
6. Дизайнеры, разработчики и система
Тут конечно есть конфликт интересов... Но имхо, у дизайнера должна быть система, по которой он работает и она должна быть логичной (например http://whitepaper.tools/)
П.Н. Небольшая демка "на коленке" Буду благодарен, если поделитесь своей системой стилизации блоков и именованием переменных.
Ответ из Telegram от @koloskof
Работа с Темати
Тематизация
Фундаментально Тематизация отвечает тому подходу, которого мы с дизайнерами придерживаемся: «При проработке визуала человек описывает смысл блока (смысловые модификаторы), ничего не зная про Тему, а при проработке Темы человек может ничего не знать про блоки, которые будут в ней отрисовываться»
Разделение внутри темы
Для каждого Мерча/Лейбла/Компании есть три Темы:
Математика цвета
Внутри каждой Темы, цветовые переменные разделены на группы.
Base
Базовые переменные, которые формируют основную палитру (они не используются в интерфейсе, нужны только для образования интерфейсных пременных (наследования)). Они имеют префикс color-base-*. Пример:
$color-base-base $color-base-system
Background
Цвета фонов, которые формируются с учётом состояний блоков. Значения наследуются от цвета переменной $color-base-essential. Они имеют префикс color-bg-*. Пример:
$color-bg-brand, $color-control-bg-default-affect
Typo
Цвета типографики, которые формируются с учётом визуальной иерархии и так же наследуются от цвета переменной $color-base-base. Они имеют префикс color-typo-*. Пример:
$color-typo-primary, $color-typo-warning
Вычисление цвета в файле темы
Каждый цвет темы вычисляется на основе изменения параметров hue(h) — тон, saturation(s) — насыщенность, lightness(l) — яркость и alpha(a) — полупрозрачность у base переменных.
Например цвет для основного текста вычисляется на основе цвета переменной $color-base-base с уменьшением альфы (полупрозрачности) до 95%:
$color-typo-primary: color($color-base-base a(95%))
Другой пример, для бордеров: $color-bg-border: color($color-base-phantom l(100%) a(20%));
Стоит помнить, что параметры переменных можно не менять. Если с точки зрения визуала цвет подходит, то base переменную допускается использовать as is. В этом случае color(...) не указывается, т.к. идет прямое наследование(?)присвоение:
$color-typo-alert: $color-base-alert;
Синтаксис математики
Альфа-канал задается всегда в %: $color-typo-primary: color($color-base-base a(95%));
Как сказано выше, для формирования нового цвета переменной, необходимо изменить один из параметров h, s, l, a. Допускается два вида записи:
h(50%) ↔️ h(50)
новыйПараметр = 0.5(текущийПараметр).
При HUE = 20, новый HUE = 0.5(20) = 10
h(+50%) (работает для + и -)
новыйПараметр = текущийПараметр + 0.5(текущийПараметр).
При HUE = 30, новый HUE = 30 + 0.5(30) = 45
Если параметр указан h(-X) и [текущийПараметр - X(текущийПараметр)]<0, то результирующее значение будет рассчитано как 0.
Пример: h(-125%), при HUE = 30, новый HUE = 30 - 1.25(30) = 30 - 37.5 = -7.5 → 0
Вложенность тем
Тема всегда должна быть примиксована как минимум на один самый верхний узел. Она задается правила как будут отрисовываться все сущности внутри одного интерфейса/экрана.
Но также всегда есть возможность делать разнотематические вставки в рамках одного интерфейса. Так же Темы могут прилетать динамически, примиксовываясь по какому-либо событию на тот или иной узел.
Темы в контролах
Контролы реагируют на уровень общей Темы так же как и все остальные интерфейсные сущности.
При этом их цветовые переменные наследуются от тех же основных переменных, что и цвета состояний блоков и типографики, вычисляются через математику (путем изменения HSL).
Благодаря Custom Properties, контролы подстраиваются под цветовую схему блока в который они помещаются (в зависимости от того, какая модификация themecolor* у ближайшего родителя). Это позволило органично вписывать Лего контролы в любые интерфейсные сущности и распространять на них единую Тему.
Создание и отладка новых тем
(тут кусок в котором взяли выборку брендов. Классифицировали их по цветам. По каждому цвету взяли самый недосвеченный и пересвеченный пример и на каждый из них попробовали написать Темы в дефолтном, инвёрсном и брендовом вариантах. Для того чтобы когда нужно описываться тему для очередного бренда можно было скопировать макисмально близкую по цвету пачку тем и немного подкрутить математику)
В чате https://t.me/whitepapertools, можно Илью более подробно про это поспрашивать
Любопытно, почему для тем (skins) нельзя использовать разные пути к css-файлам? Или вам обе темы нужны одновременно?
@viT-1 Использовать одновременно.
Решили использовать подход @whitepapertools. Дополнительно, в некоторых модификаторах использовать подход описанный https://github.com/bem-site/bem-forum-content-ru/issues/1572#issue-430090239, работающий по принципу
props/state