Написать пост

Кастомные свойства в CSS. Часть 2: особенности применения

Логотип компании Яндекс Практикум

Во второй статье серии мы подробнеее рассмотрим наследование значений кастомных свойств.

Рассказывает команда веб-факультета Яндекс.Практикума

Из первой статьи вы узнали, что такое кастомные свойства в CSS и как они работают. С их появлением у разработчиков возникли закономерные вопросы: в чём же их отличие от препроцессоров и зачем применять новый инструмент, если и старый решает основные задачи? Препроцессоры позволяют внедрить в код множество удобных абстракций: миксины, условные директивы, наследование свойств. Но препроцессоры не ориентируются в структуре DOM-дерева, поэтому их переменные не работают при некаскадном подходе в написании кода. А кастомные свойства в CSS работают, ведь значение кастомного свойства, определённого на уровне какого-либо элемента, наследуется потомками. Это их основная особенность — изучим её подробнее.

Область видимости и применение в каскаде

Кастомные свойства, как и SASS-переменные, можно задавать глобально или на уровне любого DOM-элемента.

Пример на SCSS

			$size: 300px;
$color: #1e1e1e;

.block {
  width: $size;
  height: $size;
  background-color: $color;

  &__element {
    $size: 200px;
    $color: #fff;
    width: $size;
    height: $size;
    background-color: $color;
  }
}
		

Родительский блок будет 300х300 пикселей и тёмно-серого цвета. Дочерний элемент — 200х200 пикселей и белого цвета. Теперь сделаем то же самое кастомными свойствами.

Пример на CSS

			:root {
  --size: 300px;
  --color: #1e1e1e;
}

.block {
  width: var(--size);
  height: var(--size);
  background-color: var(--color);
}

.block__element {
  --size: 200px;
  --color: #fff;
  width: var(--size);
  height: var(--size);
  background-color: var(--color);
}
		

Этот код на CSS аналогичен коду на SCSS: на глобальном уровне кастомное свойство определено для всего документа, а на уровне блока — изменено для использования внутри него.

Разницу можно почувствовать, если уйти с глобального уровня: в SCSS отказаться от определения переменных на уровне всего документа, а в CSS — от декларации кастомного свойства для корневого элемента. Дело в том, что SCSS ничего не знает о структуре DOM-дерева, ведь он не является частью платформы. SCSS заставляет разработчика объяснять ему всю структуру наследования селекторов.

Такой код на SCSS будет работать:

			.block {
  $size: 300px;
  $color: #1e1e1e;
  width: $size;
  height: $size;
  background-color: $color;

// дочерний элемент вложен в родительский 
  &__element {
    width: $size - 100px;
    height: $size - 100px;
    border: 1px solid red;
  }
}
		

А вот такой выдаст ошибку при компиляции в CSS:

			.block {
  $size: 300px;
  $color: #1e1e1e;
  width: $size;
  height: $size;
  background-color: $color;
}
// а здесь дочерний элемент записан отдельно от родительского 
.block__element {
  width: $size - 100px;
  height: $size - 100px;
  border: 1px solid red;
}
		

Результат попытки запустить компиляцию:

			Error: Undefined variable.
   ╷
   │   width: $size - 100px;
   │          ^^^^^
   ╵
		

Сделаем то же самое кастомными свойствами на CSS:

			.block {
  --size: 300px;
  --color: #1e1e1e;
  width: var(--size);
  height: var(--size);
  background-color: var(--color);
}

.block__element {
  width: calc(var(--size) - 100px);
  height: calc(var(--size) - 100px);
  border: 1px solid red;
}
		

Этот код на CSS работает как задумано. Несмотря на то, что мы использовали кастомные свойства локально, дочерние элементы родительского блока block посмотрят вверх по каскаду DOM-дерева и найдут нужные им значения.

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

Теперь, когда вы знаете, что кастомные свойства ориентируются в каскаде и управляются через JavaScript, вы сумеете легко манипулировать как вложенными, так и родительскими элементами. Например, сможете получить значение кастомного свойства по месту его декларации либо по месту его использования.

Вернёмся к примеру с блоком и вложенным элементом. Эти две строчки на JS выведут в консоль одинаковый результат:

			console.log(getComputedStyle(document.querySelector('.block')).getPropertyValue('--size'));
console.log(getComputedStyle(document.querySelector('.block__element')).getPropertyValue('--size'));
		

Теперь посмотрим, как реактивно наследуется значение переменной, применённой только к дочернему элементу:

В этом примере мы получили значение кастомного свойства, обратившись к свойствам элемента и переопределили значение на уровне элемента

А так можно, поменяв две строчки в том же коде, перенести поведение на родительский элемент:

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

Дефолтное отображение и переопределение секций

Благодаря тому, что кастомные CSS-свойства наследуют значения в каскаде, мы можем разделить отображение зон по умолчанию и зон с особенным поведением.

			/* Есть секция с карточками: что может встречаться на сайтах чаще? */
.cards-list {
  background-color: var(--cardlist-bg, #1e1e1e);
}

/* Внутри есть карточки */
.card {
  /* стили карточки */
	background-color: var(--card-bg, #fff);
}

.card::before {
  content: var(--marker-text, 'Скоро на экранах');
}
		

В этом примере кастомные свойства не заданы, а только вызваны в нужных местах. Но при этом они имеют альтернативные значения. Это значит, что пока мы не определим кастомные свойства, все созданные на странице секции с карточками будут тёмного цвета, а сами карточки — белого. На них расположится текст «Скоро на экранах» в маркере, реализованном через псевдоэлемент ::before.

Магия начинается, когда мы начинаем модифицировать секции. Предположим, на сайте появилась зона «В эфире», а внутри неё все карточки другого цвета, сама зона светлая, а в маркере появляется другая надпись «Сейчас в эфире». Такое можно реализовать одним CSS-правилом для модификатора.

			/* Вводим модификатор в HTML с типом livestream и 
задаём кастомные свойства для его наследников */
.card-list_type_livestream {
  --cardlist-bg: #fff;
  --card-bg: pink;
  --marker-text: 'Сейчас в эфире';
}
		

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

Подобных модификаторов и уровней переопределения кастомных свойств может быть сколько угодно. Посмотрите на интерактивном примере:

Все цвета на этом сайте заданы на уровне корневого элемента. Это единый центр управления палитрой сайта. Если нужно поменять цвета, всё доступно в одном месте.

Значения кастомных свойств из палитры — это альтернатива прописанным в нужных местах именам кастомных свойств без заданного значения. Получается, что значения из палитры формируют дефолтное поведение всех элементов страницы. Кастомные свойства --bg-color, --text-color, --title-color и --border-color будто ждут своего часа. Когда им назначат значения — они вступят в игру.

Для --title-color и --border-color этот момент наступает, когда мы создаём отдельную «область видимости» через HTML, внедрив туда модификатор page__section_type_canceled. Для всех потомков этой секции задаются значения кастомных свойств --title-color и –-border-color. Теперь они имеют особенное поведение.

В игру вступает JavaScript

Наследование значений кастомных свойств потомками позволяет облегчить труд по темизации веб-страниц. Мы можем задать палитру сайта глобально и пользоваться ей в любых частях сайта, переопределяя значение ключевых кастомных свойств на уровне групп элементов или элементов страницы. В примере с карточками остались неиспользованные каcтомные свойства. Они будут менять тему всей страницы. Взгляните:

В файле со скриптом создана функция changeBgAndText. Она получает на вход имена кастомных свойств из палитры и элемент, у которого нужно поменять фон и цвет текста. Кастомные свойства палитры описаны в CSS. Для простоты записи аргументов в теле функции используем шаблонные строки, чтобы не писать всё время — перед именем кастомного свойства.

При переключении чекбокса мы вызываем эту функцию сначала для блока с селектором .page, а потом для всех блоков с селектором .cards-list, передавая разные значения цветов из палитры. При первом вызове .page и его потомки станут чёрными с белым текстом, но затем цвета карточек переопределились на уровне списка карточек. Так как кастомные свойства --bg-color и --text-color используются только для определения фона и цвета текста самих карточек, но не списка, цвета изменятся только у них.

В этом примере мы показали, как можно создать тему только из палитры, которая описана кастомными свойствами на уровне корневого элемента. При этом нужно управлять только двумя параметрами, но на разных уровнях вложенности DOM-дерева.

Заключение

Вы узнали, что кастомные CSS-свойства наследуют значения в каскаде элементов. Это наделяет их другими суперспобностями, которые делают жизнь разработчика чуточку приятнее. Вместе с реактивностью — это ключевые отличия кастомных свойств от переменных в препроцессорах. Некоторые хитрости кастомных свойств значительно упрощают кастомизацию элементов и темизацию отдельных компонентов, страниц и даже всего приложения.

Но у любого супергероя есть уязвимое место. В следующей статье мы покажем, что кастомные свойства в CSS не умеют делать. Кроме этого, расскажем о сложностях, с которыми вы можете столкнуться, внедряя их в проект.

P.S. Большинство приведённых примеров кода и подходов не стоит использовать в продакшене, если вам нужно поддерживать Internet Explorer. Даже POSTCSS не сделает так, чтобы этот код хорошо работал в IE. Почему — расскажем в следующей статье.

Веб-разработка
CSS
Фронтенд
7297