После релиза CSS-каруселей в Chrome 135 перед Google I/O в прошлом году и критики доступности, новостей о развитии этого API как-то не было. Но в Chrome 140 и Chrome 154 (и соответствующих версиях Edge) CSS-карусели были немного улучшены.
Я делал пост о CSS-каруселях. Напомню, что это возможность добавить контейнеру с прокруткой кнопки «вперёд» и «назад», а также группу с маркерами (пагинацию) с помощью CSS. В сочетании со Scroll Snap получаются карусели без JS.
<ul class="carousel">
<li data-slide="1">
<!-- Слайд 1 -->
</li>
<li data-slide="2">
<!-- Слайд 2 -->
</li>
<li data-slide="3">
<!-- Слайд 3 -->
</li>
</ul>
<style>
.carousel {
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-marker-group: after;
scrollbar-width: none;
[data-slide] {
scroll-snap-align: center;
}
[data-slide]::scroll-marker {
/* Резервное значение */
content: attr(data-slide);
content: sibling-index();
}
[data-slide]::scroll-marker:target-current {
background: var(--accent);
}
&::scroll-button(left) {
content: "←" / "Previous";
}
&::scroll-button(right) {
content: "→" / "Next";
}
&::scroll-button(*):focus-visible {
outline-offset: 5px;
}
}
</style>
Одна из претензий к CSS-каруселям — создание интерактивных элементов в CSS. Кнопки, маркеры и их обёртка создаются в виде интерактивных псевдо-элементов что не даёт полного контроля и принуждает использовать
content в CSS.При просмотре в DevTools у карусели из примера выше получается такая структура:
<ul class="carousel">
::scroll-button(left)
::scroll-button(right)
::scroll-marker-group
<li data-slide="1">
::scroll-marker
<!-- Слайд 1 -->
</li>
<li data-slide="2">
::scroll-marker
<!-- Слайд 2 -->
</li>
<li data-slide="3">
::scroll-marker
<!-- Слайд 3 -->
</li>
</ul>
Внутри списка создаются псевдо-элементы
::scroll-button(*) — кнопки «вперёд» и «назад», ::scroll-marker-group — контейнер для маркеров с ролью navigation и ::scroll-marker — сами маркеры с ролью link.Эту структуру с псевдо-элементами можно выразить в виде HTML-эквивалента для понимания итоговой семантики и связей элементов. Кнопки выносятся перед списком слайдов, а контейнер с маркерами в виде
<nav> и <a> после списка.<button
aria-label="Previous"
aria-controls="[ссылается на <ul>]"
>
←
</button>
<button
aria-label="Next"
aria-controls="[ссылается на <ul>]"
>
→
</button>
<ul class="carousel">
<li data-slide="1">
<!-- Слайд 1 -->
</li>
<li data-slide="2">
<!-- Слайд 2 -->
</li>
<li data-slide="3">
<!-- Слайд 3 -->
</li>
</ul>
<nav aria-controls="[ссылается на <ul>]">
<a
href="[ссылается на <li data-slide='1'>]"
aria-details="[ссылается на <li data-slide='1'>]"
>
1
</a>
<a
href="[ссылается на <li data-slide='2'>]"
aria-details="[ссылается на <li data-slide='2'>]"
>
2
</a>
<a
href="[ссылается на <li data-slide='3'>]"
aria-details="[ссылается на <li data-slide='3'>]"
>
3
</a>
</nav>
По умолчанию контейнер с маркерами получает роль
navigation, а сами маркеры — роль link. Работает это, соответственно, как <nav> с набором <a> внутри. Изменить это поведение можно с помощью новых значений scroll-marker-group.Значения
links и tabs идут в дополнение к уже существующим значениям before и after, которые определяют визуальное положение группы маркеров. links и tabs определяют тип группы:-
links: у контейнера маркеров будет роль navigation, у маркеров будет роль link и они будут вести себя как якорные ссылки, табуляция по ссылкам будет последовательная;-
tabs: у контейнера маркеров будет роль tablist, у маркеров будет роль tab и они будут вести себя как вкладки, табуляция попадает только на активную вкладку, а навигация между вкладками с помощью стрелок.<ul class="carousel">
<li data-slide="1">
<!-- Слайд 1 -->
</li>
<li data-slide="2">
<!-- Слайд 2 -->
</li>
<li data-slide="3">
<!-- Слайд 3 -->
</li>
</ul>
<style>
.carousel {
/* ... */
scroll-marker-group: after tabs;
}
</style>
<!-- Итоговая структура в HTML-эквиваленте -->
<ul class="carousel">
<li data-slide="1">
<!-- Слайд 1 -->
</li>
<li data-slide="2">
<!-- Слайд 2 -->
</li>
<li data-slide="3">
<!-- Слайд 3 -->
</li>
</ul>
<div
role="tablist"
aria-multiselectable="false"
aria-orientation="horizontal"
aria-controls="[ссылается на <ul>]"
>
<button
role="tab"
aria-selected="true"
aria-details="[ссылается на <li data-slide='1'>]"
>
1
</button>
<button
role="tab"
aria-selected="false"
aria-details="[ссылается на <li data-slide='2'>]"
>
2
</button>
<button
role="tab"
aria-selected="false"
aria-details="[ссылается на <li data-slide='3'>]"
>
3
</button>
</div>
Контейнер меняет роль с
navigation на tablist, а маркеры с link на tab. Так как tablist — это виджет, который требует клавиатурной навигации, браузер автоматически добавляет навигацию стрелками между маркерами.Второй способ управления группой маркеров — возможность выноса в HTML. У элемента внутри карусели можно указать свойство
scroll-target-group: auto и тогда он будет использоваться в качестве контейнера для маркеров.Чтобы это работало аналогично группе в CSS, внутри контейнера должны быть якорные ссылки, которые станут аналогом псевдо-элементов
::scroll-marker. Для них браузер автоматически вычислит состояние :target-current.<div class="carousel">
<div id="slide-1">
<!-- Слайд 1 -->
</div>
<div id="slide-2">
<!-- Слайд 2 -->
</div>
<div id="slide-3">
<!-- Слайд 3 -->
</div>
<nav class="markers">
<a href="#slide-1">1</a>
<a href="#slide-2">2</a>
<a href="#slide-3">3</a>
</nav>
</div>
<style>
.carousel {
/* ... */
.markers {
scroll-target-group: auto;
a:target-current {
background-color: var(--accent);
}
}
}
</style>
По большей части это предназначено для интерактивного содержания, а не карусели. Маркеры по шаблону карусели должны быть сочетанием
tablist и tab. Если вместо ссылок будут другие элементы, то автоматика браузера не сработает.В теории это можно решить с помощью
focusgroup и ARIA, добавив клавиатурное поведение и семантику. Но от встроенного решения я жду отсутствия необходимости указывать какие-либо атрибуты ARIA для доступности.<div class="carousel" id="carousel">
<!-- Слайды -->
<nav
class="markers"
focusgroup="tablist"
aria-controls="carousel"
>
<a
href="#slide-1"
role="tab"
aria-controls="slide-1"
aria-details="slide-1"
>
1
</a>
<!-- остальные маркеры -->
</nav>
</div>
Теперь есть как минимум два способа управления клавиатурной навигацией и семантикой контейнера с маркерами. Это хорошо, но всё ещё к интерактивным псевдо-элементам в CSS остаются вопросы. Идея использовать реальные кнопки звучит лучше.
#css #ui