Малоизвестные и редко используемые функции CSS в 2022 году

alexei16/06/2022 - 09:25
Малоизвестные и редко используемые функции CSS в 2022 году

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

Итак, приступим!

Свойство all

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

  • initial

    Устанавливает все свойства в начальные значения.

  • inherit

    Устанавливает все свойства в их унаследованные значения.

  • unset

    Изменяет все значения на соответствующее значение по умолчанию, которое является либо inherit или initial.

  • revert

    Результирующие значения зависят от источника таблицы стилей, где находится это свойство.

  • revert-layer

    Результирующие значения будут соответствовать предыдущему каскадному слою или следующему соответствующему правилу.


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


h2 {
   color: var(--color-primary);
   font-size: var(--font-size-large);
   line-height: 1.5;
   text-decoration: underline;
   margin-bottom: 2rem;
}

.article h2 {
  padding: 2em;
  border-bottom: 2px solid currentColor;
}

.article__title {
  /* Нам не нужны стили из предыдущего селектора. Нам нужны только margin и font-size. */
  all: unset;
  margin-bottom: 2rem;
  font-size: var(--font-size-medium);
}

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

Применяя данное свойство, можно обнаружить интересное поведение, - цвет подчеркивания не будет обновляться до текущего назначенного цвета, до тех пор пока не будет вновь определено свойство text-decoration: underline; в селекторе .title, который содержит свойство all.


Свойство currentColor

Свойство currentColor часто называют "первой CSS переменной". currentColor это значение, равное свойству элемента color. Его можно использовать для присвоения значения, равного значению свойства color, любому свойству CSS, которое принимает значение цвета. Это принуждает CSS свойство наследовать значение свойства color.

Это значение может быть очень полезно, чтобы избежать присвоения одного и того же значения нескольким свойствам CSS, которые принимают цвет, вроде border-color, background, box-shadow и т. д. в одном и том же селекторе.


Пожалуй одним из лучших вариантов использования currentColor является стилизация встроенных элементов SVG. Всякий раз, когда мы экспортируем какую-нибудь иконку из дизайнерского набора, мы получаем с ней некие значения ее цветового оформления, определенные в дизайне. Мы можем вручную заменить все эти значения цвета при помощи currentColor, и таким образом настроить цвета SVG без необходимости заходить в разметку SVG и переопределять атрибут fill или другие цветовые атрибуты для конкретного элемента path или других элементов SVG. Это позволяет избежать громоздких и запутанных селекторов.


<!-- До -->
<path fill="#bbdb44" d="..."/>

<!-- После -->
<path fill="currentColor" d="..."/>


/* До */
.icon:hover path {
  fill: #112244;
}

/* После */
.icon {
  color: #bbdb44;
}

.icon:hover {
  color: #112244;
}


Резервное значение для пользовательского свойства

Пользовательские свойства внесли значительные улучшения в CSS, позволив разработчикам создавать повторно используемые значения в своей таблице стилей без необходимости обращаться к CSS препроцессорам, таким как SASS. Пользовательские свойства были мгновенно приняты и широко используются сегодня с большой эффективностью, особенно при создании тем и взаимодействии с JavaScript.

Тем не менее, возможность использования резервного значения часто игнорируется. Если вы не знаете, что такое резервное значение, то это второй параметр в CSS функции var, значение которого применяется, если значение первого параметра не задано.


color: var(--color-icon, #9eeb34);

В качестве резервного значения также можно использовать другую переменную:


color: var(--color-icon-primary, var(--color-icon-default));

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

Это вторичное значение также позволяет элегантно переопределять цвета темы без увеличения специфичности.

Мы можем легко изменить значения пользовательских переменных, переопределив их.


:root {
  --theme-color-background: #f5f5f5;
  --theme-color-text: #111111;
}

/* Глобальное переопределение в родительском классе элемента <body> или <html> */
.theme--dark {
  --theme-color-background: #111111;
  --theme-color-text: #f5f5f5;
}

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


.box {
    color: var(--color-theme-default);
}

.theme--dark .box {
  color: var(--color-component-override);
}

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


:root {
--theme-color-default: darkgoldenrod;
--color-some-other-color: cyan;
}

.theme--dark {
/* Темная тема */
  --color-component-override: var(--color-some-other-color);
}

.box {
  color: var(--color-component-override, var(--theme-color-default));
}


Счетчики

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

  • counter-reset

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

  • reversed

    Функция, используемая при определении счетчика при помощи counter-reset. Направляет отсчет назад, а не вперед.

  • counter-increment

    Определяет значение изменения счетчика (если счетчик определен как reversed или передается задано отрицательное значение, то счетчик будет уменьшаться, а не увеличиваться). По умолчанию счетчик будет увеличиваться на 1, но этому свойству также может быть передано пользовательское значение.

  • counter

    Используется для доступа к значению счетчика. Обычно используется в свойстве content.

В следующем примере мы инициализируем два счетчика - articles, который подсчитывают основные разделы, и notes, который считает заметки на странице. В одном разделе может быть несколько заметок.


А что если мы хотим определить, какая заметка принадлежит какому разделу на странице? Нам нужно добавить номер раздела к каждой заметке. Например, вторая заметка третьего раздела - "Note 3.2.".

Мы можем легко настроить инициализацию и отображение счетчиков заметок. При этом в одном свойстве content, можно использовать несколько значений счетчиков.


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

Медиа запросы по способу взаимодействия

При создании адаптивных сайтов мы часто делаем предположения об устройствах ввода, основываясь на размере экрана пользователя. Мы предполагаем, что экран с размером в 1920px скорее всего принадлежит настольному компьютеру или ноутбуку, и пользователь будет взаимодействовать с сайтом с помощью мыши и клавиатуры, но как быть с ноутбуками с сенсорным экраном или с экранами Smart TV?

Вот здесь и приходят на помощь медиа запросы, основанные на способе взаимодействия, позволяющие точно настроить поведение компонентов, с которыми пользователи могут взаимодействовать (поля ввода, вынесенные меню, выпадающие списки, всплывающие окна и т. д.) и зависящие от основного механизма ввода - касание, стилус, указатель мыши и т. д.


@media (pointer: fine) {
  /* используется мышь или стилус */
}
@media (pointer: coarse) {
  /* используется касание */
}
@media (hover: hover) {
  /* курсор может наводиться */
}
@media (hover: none) {
  /* курсор не может наводиться */
}

Свойство aspect-ratio для контроля размера

С помощью свойства aspect-ratio мы можем легко контролировать размер элемента. Например, кнопки с равными шириной и высотой будут иметь соотношение сторон 1. Таким образом, мы можем легко создавать кнопки, которые адаптируются к их содержимому и к различным размерам значков, сохраняя при этом необходимую форму.

Улучшенные градиенты

Градиенты используются уже довольно давно, и они стали распространенным дизайнерским приемом. Однако, как отмечает Джош У. Комо (Josh W. Comeau), средняя часть градиента иногда может выглядеть серой и размытой, в зависимости от используемых цветов.

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

Во втором градиенте используется метод интерполяции с несколькими средними точками, которые генерируются с помощью инструмента генератора градиентов Джоша У. Комо. Обратите внимание, что теперь средняя часть имеет более насыщенный желтый и оранжевый оттенок, и она выглядит гораздо более яркой и красивой, чем в первом случае.

Псевдоселекторы :where и :is

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

Оба этих селектора имеют дело с группировкой и спецификацией. И начнем мы с псевдоселектора :is.

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


ol li,
ul li {
  margin-bottom: 0.25em;
}

ol ol,
ul ul,
ol ul,
ul ol {
  margin: 0.25em 0 1em;
}

С помощью псевдоселектора :is мы можем легко превратить эти селекторы в одно выражение.


:is(ol,ul) li {
  margin-bottom: 0.25em;
}

:is(ol,ul) :is(ol,ul) {
  margin: 0.25em 0 1em;
}


Псевдоселектор :where работает так же, как псевдоселектор :is, но при этом снижает специфичность выражения до нуля. Почему это важно? Давайте вернемся к нашему примеру и немного изменим разметку. Введем селектор .list, чтобы можно было добавлять стили к спискам, назначая класс. Также, добавим дополнительный класс для вложенного списка .list-highlight, который задает цвет фона и регулирует отступы и поля, поэтому вложенный список будет выглядеть более заметным.


/* Стили по умолчанию для вложенных списков */
.list :is(ol,ul) {
  margin: 0.25em 0 1em;
}

/* Дополнительный класс для вложенного списка */
.list-highlight  {
  background: #eeeeee;
  padding: 1em 1em 1em 2em;
  margin: 0.5em 0;
}

Однако, когда мы применим класс .list-highlight к любому из вложенных списков, отступов не будет, потому что этот стиль не применяется. Что же происходит?

Результирующая специфичность для певдоселектора :is будет самой высокой. Таким образом, стили из нашего класса .list-highlight никогда не перекроют его.

Нам нужно избежать увеличения специфичности и добавления зависимостей для дополнительных служебных классов, поэтому давайте заменим :is на :where и посмотрим, что произойдет.


.list :where(ol,ul) {
  /* ... */
}


Наш дополнительный класс работает без необходимости определения более высокой специфичности или других переопределений! Псевдоселектор :where устанавливает специфичность селекторов в списке на ноль и позволяет переопределить стили по умолчанию.

Мы можем использовать псевдоселекторы :is и :where, чтобы сгруппировать несколько селекторов в одно выражение. С помощью псевдоселектора :where мы можем задать безопасные стили по умолчанию со сложными селекторами, которые можно легко переопределить простыми служебными классами без необходимости увеличения специфичности.

Свойство scroll-padding

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

К счастью, нам больше не нужно полагаться на JavaScript. Мы можем определить scroll-padding-top и изменить его значение, используя стандартные медиа запросы CSS.


html {
  scroll-padding-top: 6rem;
  scroll-behavior: smooth;
}


Мы также можем установить другие направления или использовать общее свойство scroll-padding.


scroll-padding: /* ... */;

scroll-padding-top: /* ... */;
scroll-padding-right: /* ... */;
scroll-padding-bottom: /* ... */;
scroll-padding-left: /* ... */;

Параметры рендеринга шрифта

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

Числовые символы шрифта Fira Sans
Числовые символы шрифта Fira Sans имеют разную ширину (на второй строчке на один символ больше)

CSS свойство font-variant-numeric: tabular-nums способно решить эту проблему, устанавливая одинаковую ширину для всех числовых символов шрифта.


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

Также, существует CSS свойство font-variant, которое позволяет нам задействовать еще больше функций для всех символов, а не только для числовых.

Вот еще несколько примеров свойства font-variant-numeric при использовании со шрифтом Source Sans 3.


Создание стекового контекста при помощи isolate

Свойство isolate позволяет разделять стеки z-index.

Возможно, вам знакома такая ситуация, когда вы, например, добавляли многоразовый компонент всплывающей подсказки на свою страницу и видели, что у него значение z-index меньшее, чем у какого-нибудь соседнего элемента. В результате всплывающая подсказка отображалась за ним. Обычно, эта проблема решалась увеличением значения z-index компонента всплывающей подсказки, но это потенциально грозит регрессией и аналогичными проблемами где-то еще в проекте.

Именно это и демонстрирует следующий пример. Здесь всплывающая подсказка заблокирована в активированном состоянии для демонстрационных целей.


Давайте посмотрим, что здесь происходит. Разработчик создал стилизованный компонент заголовка, у которого имеется фоновый декоративный элемент, как определено в дизайне. Но здесь перемудрили со значениями z-index:

  • у текста заголовка значение z-index: 2;
  • у фонового декоративного элемента значение z-index: 1;

Этот компонент работает так, как и ожидалось, и был полноценно добавлен в основной код. Через некоторое время кто-то еще сделал компонент всплывающей подсказки со значением z-index: 1;. Не было явных причин присваивать более высокое значение z-index, так как всплывающая подсказка должна быть чуть выше текста. Через некоторое время произошел крайний случай, когда текст заголовка оказался над всплывающей подсказкой.

Мы могли бы повозиться со значениями z-index для компонента заголовка и компонента всплывающей подсказки или же назначить z-index их родительским элементам со свойством position: relative, чтобы создать новый стековый контекст.

Однако, давайте подумаем о проблеме по-другому - что, если бы мы могли создать новый стековый контекст, не жонглируя z-index? А это именно то, что свойство isolation: isolate и делает! Оно создает новый стековый контекст или группу. Оно говорит браузеру не смешивать эти две группы стеков, даже если значение z-index для заголовка будет увеличено до максимально возможного значения. Таким образом, мы можем сохранить низкие значения z-index и не беспокоиться, что его значение должно быть 2, 10, 50, 100, 999999, и так далее.

Давайте создадим новый стековый контекст в корне нашего компонента заголовка и в корне нашего компонента всплывающей подсказки и посмотрим, что произойдет.


.title {
  isolation: isolate;
  /* ... */
}

.tooltip-root {
  isolation: isolate;
  /* ... */
}


Мы исправили проблему, изолировав стековые контексты для наших двух конфликтующих компонентов, не возясь со значениями для z-index.

Скриншот с визуализацией присвоения большого значения для стека у текста заголовка
Даже если назначить тексту заголовка излишне высокое значение, например 99999, это не повлияет на другую изолированную группу - всплывающая подсказка все равно будет отображаться над заголовком.

Оптимизация производительности рендеринга

Проблема производительности рендеринга, когда речь идет об обычных проектах, возникает весьма редко. Однако в тех редких случаях, когда создаются большие деревья DOM с несколькими тысячами элементов, то можно столкнуться с некоторыми проблемами производительности, связанными с CSS и рендерингом. К счастью, у нас есть прямой способ справиться с этими проблемами, которые вызывают лаги, невосприимчивость к пользовательскому вводу, низкому FPS и т. д.

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


.container {
/* дочерние элементы не будут отображаться вне этого контейнера,
поэтому рендерить нужно только одержимое этого контейнера */
  contain: paint;
}

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

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

Тем не менее, есть опция, когда мы можем сигнализировать браузеру автоматически применять требуемое значение свойства contain. Для этого мы можем использовать свойство content-visibility. С помощью этого свойства мы можем отложить рендеринг содержимого, находящегося за пределами экрана. Некоторые даже называют это "ленивым рендерингом".


.story {
  content-visibility: auto; /* Поведение как у overflow: hidden; */
  contain-intrinsic-size: 100px 1000px;
}

С помощью свойства contain-intrinsic-size мы можем оценить размер раздела, который будет рендериться. Без этого свойства размер контента был бы 0, и длина видимой страницы продолжала бы увеличиваться по мере загрузки контента.


Обратите внимание, как в примере прыгает бегунок на полосе прокрутки, когда вы прокручиваете содержимое. Это связано с разницей между размером заполнителя (расчетным) contain-intrinsic-size и фактическим размером рендеринга. Если мы опустим это свойство, скачки бегунка на полосе прокрутки будут еще более резкими.

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

Заключение

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