![](https://habrastorage.org/webt/ma/po/lv/mapolvqq4uunxfqoaviv3g9km9y.jpeg)
Последние несколько месяцев открыли золотую эру веб UI (User Interface — пользовательский интерфейс).
Вот 20 самых захватывающих и впечатляющих возможностей, которые появились недавно или появятся в ближайшем будущем:
- запросы контейнера (container queries);
- запросы стиля (style queries);
- селектор
:has()
; - микросинтаксис
nth-of
; text-wrap: balance
;initial-letter
;- динамические единицы измерения области просмотра (viewport);
- цветовые пространства с широкой гаммой;
color-mix()
;- вложенность селекторов (nesting);
- каскадные слои (cascade layers);
- стили с ограниченной областью видимости (scoped styles);
- тригонометрические функции;
- индивидуальные свойства трансформации;
popover
;- позиционирование якоря (anchor positioning);
selectmenu
;- дискретные свойства переходов;
- анимации, управляемые прокруткой (scroll-driven animations);
- переходы отображения (view transitions).
Отзывчивость
Начнем с новых возможностей адаптивного дизайна. Эти возможности позволяют разрабатывать логические интерфейсы, владеющие информацией о собственных стилях, а также интерфейсы, использующие возможности системы для обеспечения большей "нативности" и позволяющие пользователю стать частью процесса настройки дизайна.
Запросы контейнера
Запросы контейнера недавно стали стабильными во всех современных браузерах. Они позволяют запрашивать размеры и стили родительского элемента для определения стилей его дочерних элементов. Медиа-запросы (media queries) позволяют получать информацию только об области просмотра, т.е. они могут работать только на макроуровне макета (layout) страницы. С другой стороны, запросы контейнера являются более точным инструментом, поддерживающим любое количество макетов или макетов внутри других макетов (вложенных макетов).
В следующем примере список элементов Primary
и боковая панель Favorites
являются контейнерами. Колонка со временем получения электронного письма в этих компонентах отображается и скрывается в зависимости от наличия свободного пространства. Это одни и те же компоненты, отображаемые в разных представлениях.
Стили этих компонентов являются динамическими благодаря запросам контейнера. При изменении размеров или макета страницы компоненты реагируют на изменение доступного им пространства. Боковая панель становится верхней с большей шириной, макеты обоих компонентов становятся идентичными. При недостатке свободного пространства они оба отображаются в сжатом виде.
Подробнее о запросах контейнера и разработке логических компонентов можно почитать в этой статье.
Запросы стиля
Спецификация запросов контейнера также позволяет запрашивать значения стилей родительского элемента. В настоящее время данная возможность частично реализована в Chrome 111, где для применения стилей контейнера можно использовать переменные CSS.
В следующем примере используются характеристики погоды, хранящиеся в значениях переменных, такие как дождь, солнечно и облачно, для стилизации фона карточки и иконки индикатора.
@container style(--sunny: true) {
.weather-card {
background: linear-gradient(-30deg, yellow, orange);
}
.weather-card:after {
content: url(<data-uri-for-demo-brevity>);
background: gold;
}
}
![](https://habrastorage.org/webt/ri/hn/zt/rihnzt3movg-qagejbxtvm3k2vw.png)
Это только начало для запросов стилей. В будущем у нас появятся логические запросы (boolean queries) для определения наличия значения переменной и уменьшения повторяемости кода. В настоящее время также обсуждаются запросы диапазона (range queries) для применения стилей на основе диапазона значений.
Подробнее о запросах стилей можно почитать в этой статье.
:has()
Селектор :has() является, пожалуй, самой мощной новой возможностью CSS, поддерживаемой всеми современными браузерами. Он позволяет применять стили на основе наличия в родительском элементе определенных дочерних элементов или определенного состояния дочерних элементов. Это означает, что теперь в нашем распоряжении имеется родительский селектор (parent selector).
В примере запросов контейнера :has()
используется для того, чтобы сделать компоненты еще более динамичными. Например, фон элемента списка с дочерним элементом, имеющим класс star
, становится серым, а фон элемента с выбранным чекбоксом — синим.
![](https://habrastorage.org/webt/df/ot/vo/dfotvok7iwjcnfnxctdjepaj_cs.png)
Следует отметить, что :has()
не ограничен выбором родительского элемента. Он также позволяет выбрать любого потомка родительского элемента. Например, заголовок элемента списка с дочерним элементов, имеющим класс star
, становится жирным. Это реализовано с помощью .item:has(.star) .title
. :has()
предоставляет доступ к родительским элементам, дочерним элементам и даже к соседним элементам (сиблингам), что делает этот интерфейс очень гибким. Новые примеры его использования появляются каждый день.
Во избежание проблем с производительностью рендеринга в больших деревьях DOM рекомендуется максимально ограничивать область видимости:has()
. Например, поиск совпадений с помощью:has()
применительно к корневому элементу (root) будет медленнее, чем применительно к панели навигации или карточки элемента.
Подробнее о селекторе :has()
можно почитать в этой статье.
nth-of
Продвинутый синтаксис nth-child
позволяет использовать ключевое слово of
для ограничения существующего микросинтаксиса An + B
определенным набором элементов.
При использовании обычного nth-child
, например, :nth-child(2)
на классе special
, браузер выберет элемент с классом special
и также второго потомка. :nth-child(2 of .special)
сначала отфильтрует все элементы с классом special
, затем возьмет второй элемент из этого списка.
text-wrap: balance
Начиная с Chrome 114, вы можете использовать сбалансированный перенос текста для заголовков с помощью свойства text-wrap
со значением balance
.
Для балансировки текста браузер выполняет эффективный бинарный поиск наименьшей ширины, которая не вызывает переноса строки, останавливаясь на одном пикселе CSS (не экранном пикселе). Для дальнейшей оптимизации бинарного поиска браузер начинает с 80% средней ширины строки.
Несмотря на то, что это отличное прогрессивное улучшение, которое можно попробовать уже сегодня, следует отметить, что данный интерфейс работает только до 4 строк текста, поэтому он отлично подходит для заголовков и подзаголовков, но не подходит для более длинного контента.
Подробнее о балансировке текста можно почитать в этой статье.
initial-letter
Поддержка — 84.99% (без префикса — 64.67%)
Другим приятным улучшением веб-типографии является initial-letter
.
initial-letter
используется на псевдоэлементе :first-letter
для определения размера первой буквы на основе того, сколько строк она занимает, т.е. для определения блочного смещения буквы или "раковины" (sink), в которой она располагается.
Подробнее о initial-letter
можно почитать в этой статье.
Динамические единицы измерения области просмотра
Одной из распространенных проблем веб-разработки является "полноразмерность" (full-viewport size), особенно на мобильных устройствах. Например, мы хотим 100vh
(100% высоты области просмотра), что означает "будь таким же высоким, как область просмотра", но единица измерения vh
не учитывает такие вещи, как, например, автоматически скрываемые навигационные панели на мобильных устройствах, поэтому иногда высота становится слишком большой или появляется прокрутка.
![](https://habrastorage.org/webt/8e/b9/xs/8eb9xsud7tp0xgjc3u14b4ljfr8.png)
Теперь в нашем распоряжении имеются новые единицы измерения:
svh
иsvw
(small viewport height/width), представляющие наименьший активный размер области просмотра;lvh
иlvw
(large), представляющие наибольший активный размер области просмотра;dvh
иdvw
(dynamic).
Динамические единицы измерения автоматически получают новые значения при отображении/скрытии дополнительных браузерных панелей, таких как поиск сверху или панель управления снизу.
![](https://habrastorage.org/webt/rf/t8/c1/rft8c1ezk841ajg_dqpevtmsvqw.png)
Подробнее о новых единицах измерения размеров области просмотра можно почитать в этой статье.
Цветовые пространства с широкой гаммой
До того, как цветовые пространства с широкой гаммой (wide-gamut color spaces) стали доступны на веб-платформе, мы могли взять фотографию с яркими (vivid) цветами и отобразить ее на любом современном устройстве, но мы не могли получить кнопку, текст или фон с такими же яркими цветами.
Теперь у нас есть диапазон новых цветовых пространств, включая REC2020, P3, XYZ, LAB, OKLAB, LCH и OKLCH. Подробнее о них можно почитать в этом руководстве.
![](https://habrastorage.org/webt/fk/7x/ek/fk7xekuyyjnkiibti5u6btbd55s.png)
На изображении ниже белой линией отмечена граница между диапазоном srgb и диапазоном пространств с широкой гаммой.
![](https://habrastorage.org/webt/j1/67/ta/j167tazgeunh2i582jjfghhzpbi.png)
Adam Argyle разработал инструмент для выбора цветов и градиентов, позволяющий поэкспериментировать с новыми цветами.
color-mix()
Новая функция color-mix
позволяет смешивать значения двух цветов для создания новых значений на основе каналов смешиваемых цветов. На результат влияет пространство цветов, в котором осуществляется смешивание.
color-mix(in srgb, blue, white);
color-mix(in srgb-linear, blue, white);
color-mix(in lch, blue, white);
color-mix(in oklch, blue, white);
color-mix(in lab, blue, white);
color-mix(in oklab, blue, white);
color-mix(in xyz, blue, white);
![](https://habrastorage.org/webt/pe/4l/1i/pe4l1ibwrs4hdqp42mxjlcm_zyw.png)
color-mix()
предоставляет долгожданную возможность: возможность сохранять непрозрачные (opaque) значения цвета при добавлении к ним некоторой прозрачности (transparency). Теперь мы можем использовать наши переменные с цветами бренда для создания вариаций цвета с разной прозрачностью. Это делается с помощью смешивания цвета с прозрачностью. Например, когда мы смешиваем синий цвет с 10% прозрачностью, то получаем 90%-непрозрачный синий цвет. Это позволяет легко и быстро создавать цветовые системы.
Это можно увидеть в действии в Chrome DevTools на панели "Стиль" (style pane).
![](https://habrastorage.org/webt/by/gd/ez/bygdezfbhsxi2-uuvdq_dcimwqk.png)
Больше примеров можно найти в этой статье, а поиграть с color-mix()
можно здесь.
Основы CSS
Возможности, рассчитанные на пользователя, это лишь одна часть уравнения, многие возможности имеют целью улучшение опыта разработки, создание надежной и организованной структуры CSS. Эти возможности включают в себя вложенность CSS, каскадные слои, стили с ограниченной областью видимости и индивидуальные свойства трансформации.
Вложенность
Вложенность CSS — то, что нам по-прежнему нравится в Sass, и одна из наиболее востребованных возможностей. Вложенность позволяет разработчикам писать код в более сжатом, сгруппированном виде, что уменьшает избыточность кода.
/* без вложенности */
.card {}
.card:hover {}
/* с ней */
.card {
&:hover {}
}
Вложенными могут быть не только селекторы, но также медиа-запросы, а, значит, и запросы контейнера. В следующем примере макет карточки меняется с портретного на ландшафтный при наличии достаточной ширины ее контейнера:
.card {
display: grid;
gap: 1rem;
@container (width >= 480px) {
display: flex;
}
}
Свойство display
получает значение flex
, когда контейнер имеет ширину (горизонтальное пространство), большую или равную 480px
.
Подробнее о воженности можно почитать в этой статье.
Каскадные слои
Другой болевой точкой веб-разработки является обеспечение согласованного применения одних стилей вместо других.
Каскадные слои решают эту проблему, предоставляя пользователю управление над тем, какие слои имеют более высокий приоритет. Это означает более гранулированный подход к применению стилей.
![](https://habrastorage.org/webt/9f/sa/6u/9fsa6u6it9fwwqic-vflialkg6g.png)
Подробнее о каскадных слоях можно почитать в этой статье.
Стили с ограниченной областью видимости
Такие стили позволяют разработчикам определять границы, в которых они применяются, т.е. создавать пространства имен (namespaces) CSS. Раньше разработчики полагались на сторонние скрипты для переименования классов или специальные соглашения об именовании для предотвращения коллизий стилей. Скоро для этого можно будет использовать @scope
.
Здесь мы ограничиваем элемент .title
элементом .card
— стили заголовка карточки не будут конфликтовать со стилями других заголовков:
@scope (.card) {
.title {
font-weight: bold;
}
}
В следующем демо можно увидеть совместное использование @scope
и @layer
:
![](https://habrastorage.org/webt/2h/lu/ix/2hluixaoguaivegmfzquqcfa94u.png)
Узнать больше о @scope
можно в этой спецификации.
Тригонометрические функции
В существующие математические функции CSS были добавлены тригонометрические функции. Они поддерживаются всеми современными браузерами и позволяют создавать более органичные макеты. Одним из отличных примеров является приведенный ниже радиальный макет меню, который создан с помощью функций sin()
и cos()
.
В следующем демо круги вращаются вокруг центральной точки. Вместо того, чтобы вращать каждый круг вокруг его центра и затем смещать его наружу, каждый круг смещается по осям X и Y. Расстояние по осям определяется путем передачи --angle
функциям cos()
и sin()
, соответственно.
Подробнее о тригонометрических функциях можно почитать в этой статье.
Индивидуальные свойства трансформации
Раньше мы полагались на функцию transform
для применения функций масштабирования, вращения и смещения элемента UI. Это приводило к дублированию кода, что было особенно удручающим в случае применения нескольких трансформаций в разное время анимации.
.target {
transform: translateX(50%) rotate(30deg) scale(1.2);
}
.target:hover {
transform: translateX(50%) rotate(30deg) scale(2); /* Изменился только масштаб (scale), но мы должны повторить все функции */
}
Теперь трансформации можно разделять и применять по отдельности:
.target {
translate: 50% 0;
rotate: 30deg;
scale: 1.2;
}
.target:hover {
scale: 2;
}
Подробнее об индивидуальных свойствах трансформации можно почитать в этой статье.
Кастомизируемые компоненты
Для удовлетворения ключевых потребностей разработчиков мы работаем с группой сообщества OpenUI и решили начать с разработки трех вещей:
- встроенной функциональности попапа (модального/всплывающего окна) с обработчиками событий, декларативной структурой DOM и доступностью по умолчанию;
- интерфейса CSS для связывания двух элементов — позиционирования якоря;
- кастомизируемого компонента выпадающего меню, позволяющего стилизовать содержимое элемента
select
.
popover
Интерфейс popover предоставляет такую встроенную браузерную магию, как:
- поддержка слоя верхнего уровня (top-layer), что избавляет от необходимости управления
z-index
. При открытииpopover
илиdialog
этот элемент перемещается на специальный слой, находящийся на верхнем уровне страницы; - бесплатное легкое закрытие (light-dismiss) в
auto
"поповерах", когда клик за пределами элемента приводит к его закрытию, удалению из дерева доступности и правильному управлению фокусом; - дефолтная доступность для установления связи между целью поповера (popover target) и самим поповером.
Все это означает меньше JavaScript для создания такого функционала и управления его состояниями.
![](https://habrastorage.org/webt/lz/c9/hj/lzc9hjolwermttqlwuror_evpra.png)
Структура DOM является декларативной — элемент поповера определяется с помощью атрибутов id
и popover
. Затем этот id
используется в качестве значения атрибута popovertarget
элемента, открывающего поповер, такого как кнопка:
<div id="event-popup" popover>
<!-- Содержимое поповера -->
</div>
<button popovertarget="event-popup">Создать новое событие</button>
popover
является сокращением для popover="auto"
. Элемент с popover="auto"
закрывает другие поповеры и получает фокус при открытии, а также может быть легко закрыт. Противоположностью такого элемента является элемент с popover="manual"
.
Позиционирование якоря
Поповеры часто используются в таких элементах, как диалоги и тултипы (всплывающие подсказки), которые, как правило, привязываются к другим элементам. Рассмотрим пример. При клике на событие в календаре рядом с событием появляется диалог. Элемент календаря — это якорь, а поповер — это диалог, в котором отображаются детали события.
Для создания отцентрованого тултипа можно применить функцию anchor
, используя ширину якоря для позиционирования тултипа на 50% позиции якоря по оси X. Затем применяются другие стили, определяющие положение тултипа.
Но что произойдет, если поповер не помещается в область просмотра?
![](https://habrastorage.org/webt/jy/8i/py/jy8ipyj0q2ib7wwufksyjc7d6vo.png)
Для решения этой проблемы интерфейс позиционирования якоря предоставляет возможность кастомизации резервных (fallback) позиций. В следующем примере создается резервная позиция "top-then-bottom". Браузер сначала попытается позиционировать тултип сверху, а если он не помещается в область просмотра, браузер помещает его под элемент-якорь.
.center-tooltip {
position-fallback: --top-then-bottom;
translate: -50% 0;
}
@position-fallback --top-then-bottom {
@try {
bottom: calc(anchor(top) + 0.5rem);
left: anchor(center);
}
@try {
top: calc(anchor(bottom) + 0.5rem);
left: anchor(center);
}
}
Подробнее о позиционировании якоря можно почитать в этой статье.
selectmenu
Поповер и позиционирование якоря позволяют разрабатывать полностью настраиваемые меню выбора (selectmenu). Группа OpenUI занимается исследованием фундаментальной структуры таких меню и ищет способы кастомизации любого содержимого внутри них. Рассмотрим эти примеры:
![](https://habrastorage.org/webt/uv/ff/i3/uvffi3o0jsdqkntemcn_fhp9jjy.png)
Разметка левого меню (с цветными точками) может выглядеть следующим образом:
<selectmenu>
<button slot="button" behavior="button">
<span>Выберите тип события</span>
<span behavior="selected-value" slot="selected-value"></span>
<span><img src="icon.svg"/></span>
</button>
<option value="meeting">
<figure class="royalblue"></figure>
<p>Встреча</p>
</option>
<option value="break">
<figure class="gold"></figure>
<p>Обед/перерыв</p>
</option>
<!-- ... -->
</selectmenu>
Дискретные свойства переходов
Для плавного отображения и скрытия поповеров нужен какой-то способ анимирования дискретных свойств. Раньше эти свойства, как правило, не могли анимироваться. Речь идет о таких свойствах, как z-index
и display
.
Частью работы по обеспечения плавных переходов для поповеров, меню выбора и даже существующих элементов, вроде диалогов и кастомных компонентов является поддержка браузерами этих анимаций.
В следующем примере открытие и закрытие поповера анимируется с помощью :popover-open
для открытого состояния, @starting-style
для состояния перед открытием и применения трансформаций для состояния после открытия. Для того, чтобы это работало с display
, данное свойство должно быть явно указано в свойстве transition
:
.settings-popover {
&:popover-open {
/* 0. Перед открытием */
@starting-style {
transform: translateY(20px);
opacity: 0;
}
/* 1. Открытое (измененное) состояние */
transform: translateY(0);
opacity: 1;
}
/* 2. Состояние после открытия (изменения) */
transform: translateY(-50px);
opacity: 0;
/* Перечисляем анимируемые свойства, включая `display` */
transition: transform 0.5s, opacity 0.5s, display 0.5s;
}
Взаимодействия
Последним набором новых "фич" являются возможности по взаимодействию. Речь идет об анимации, управляемой прокруткой, и переходах отображения.
Анимации, управляемые прокруткой
Анимации, управляемые прокруткой (scroll-driven animations), позволяют управлять проигрыванием анимации на основе позиции прокрутки прокручиваемого контейнера. Это означает, что при прокрутке вверх и вниз, анимация проигрывается вперед и назад. Данный интерфейс также позволяет управлять анимацией на основе позиции элемента внутри прокручиваемого контейнера. Это позволяет создавать интересные эффекты, такие как фоновые изображения с эффектом параллакса, индикаторы прогресса прокрутки и изображения, отображаемые при попадании в область просмотра.
Рассматриваемый интерфейс предоставляет набор классов JavaScript и свойств CSS, позволяющих легко создавать анимации, управляемые прокруткой.
Для управления анимацией CSS с помощью прокрутки используются новые свойства scroll-timeline
, view-timeline
и animation-timeline
. Для управления JavaScript Web Animations API в Element.animate()
в качестве настройки timeline
передается экземпляр ScrollTimeline
или ViewTimeline
.
Новые интерфейсы работают вместе с существующими интерфейсами веб-анимаций и анимаций CSS. Они имеют те же преимущества, включая возможность запуска анимаций за пределами основного потока (main thread).
Подробнее об анимации, управляемой прокруткой, можно почитать в этой статье.
Переходы отображения
Интерфейс переходов отображения (View Transition API) позволяет модифицировать DOM за один шаг путем создания перехода между двумя состояниями. Это может быть реализовано через прозрачность (fade) всей страницы, либо каждая часть страницы может анимироваться отдельно.
Переходы отображения могут использоваться в качестве прогрессивного улучшения: достаточно обернуть код, обновляющий DOM, в данный интерфейс с резервом для браузеров, которые не поддерживают эту фичу.
function spaNavigate(data) {
// Страховка для браузеров, не поддерживающих этот API:
if (!document.startViewTransition) {
updateTheDOMSomehow(data);
return;
}
// Переход отображения:
document.startViewTransition(() => updateTheDOMSomehow(data));
}
Характер перехода определяется с помощью CSS:
@keyframes slide-from-right {
from { opacity: 0; transform: translateX(75px); }
}
@keyframes slide-to-left {
to { opacity: 0; transform: translateX(-75px); }
}
::view-transition-old(root) {
animation: 350ms both slide-to-left ease;
}
::view-transition-new(root) {
animation: 350ms both slide-from-right ease;
}
Как показано в этом замечательном демо от Maxi Ferreira, другие взаимодействия со страницей, такие как воспроизведение видео, продолжают работать во время перехода отображения.
Подробнее о переходах отображения можно почитать в этом руководстве.