Запросы контейнера состояния прокрутки позволяют стилизовать элементы на основе их текущего состояния прокрутки, например, является ли элемент "застрявшим" (через липкое позиционирование) или "привязанным" (через привязку прокрутки). Это позволяет создавать карусели и галереи, в которых активный элемент можно визуально выделить без использования наблюдателей пересечения JavaScript или прослушивателей событий прокрутки.
Базовая реализация
Чтобы выделить привязанные элементы, необходимо создать контейнер scroll-snap, определить целевые объекты привязки как контейнеры scroll-state, а затем запросить это состояние для стилизации дочерних элементов.
1. Создайте контейнер с привязкой прокрутки
В родительском контейнере должен быть включен параметр scroll-snap-type.
<div class="carousel">
<div class="carousel-item">
<div class="card">Product 1 content</div>
</div>
<div class="carousel-item">
<div class="card">Product 2 content</div>
</div>
</div>
.carousel {
display: flex;
overflow-x: auto;
/* MANDATORY: Enable scroll snapping on the container */
scroll-snap-type: x mandatory;
}
2. Определите цели привязки как контейнеры состояния прокрутки.
Каждый элемент карусели, для которого нужно отслеживать привязку, должен быть объявлен как контейнер scroll-state.
.carousel-item {
/* Define where the item snaps within the container */
scroll-snap-align: center;
/* MANDATORY: Establish this element as a scroll-state query container */
container-type: scroll-state;
}
3. Запросить состояние snapped
Поскольку запросы контейнера задают стиль потомков, вам нужно применить стили выделения к элементу внутри целевого объекта привязки. Поскольку контейнер прокрутки настроен на переполнение по оси X, используйте запрос scroll-state(snapped: x).
ОБЯЗАТЕЛЬНО. Оберните стили в @media (prefers-reduced-motion: no-preference), чтобы показывать эффект только пользователям, которые не запрашивали уменьшение движения. В зависимости от ситуации вы можете сохранить некоторые части эффекта, но в этом случае карточки будут мигать белым и синим цветом, что может вызвать проблемы у некоторых пользователей, поэтому мы полностью отключаем его.
/* Specify transition outside of queries so that it is applied regardless of state. */
.card {
transition:
scale 0.4s cubic-bezier(0.25, 0.8, 0.25, 1),
background-color 0.4s,
color 0.4s,
box-shadow 0.4s;
}
/*
Only show the effect for users not requesting reduced motion. Disable completely, including the color change, as it causes a flash that may be problematic.
*/
@media (prefers-reduced-motion: no-preference) {
/* Style the content when its parent .carousel-item is snapped on the x axis */
@container scroll-state(snapped: x) {
.card {
background: #007bff;
color: white;
scale: 1.15;
box-shadow: 0 10px 25px rgba(0, 123, 255, 0.3);
}
}
}
/* MANDATORY Copy-Paste Safety: Disable highlight scaling/flashing for motion sensitive users */
@media (prefers-reduced-motion: reduce) {
.card {
transition: none !important;
scale: 1 !important;
}
}
Дескриптор snapped может запрашивать определенные оси: x, y, inline, block или both.
Специальные возможности
AVOID использовать scroll-state с интерактивными элементами.
Визуальное выделение привязанных элементов может улучшить UX, но привязанный элемент не отображается в дереве специальных возможностей. Визуальная тема, примененная к прикрепленному элементу, не должна указывать на то, что элемент активен или находится в фокусе, а кольцо фокуса клавиатуры должно быть хорошо видно и отличаться от выделения snapped. Если привязанный элемент интерактивный, используйте другие стандартные методы, чтобы сделать его доступным.
Привязка происходит из-за прокрутки, которая не перемещает фокус клавиатуры. Однако фокус клавиатуры может привести к перемещению контейнера прокрутки, что приведет к изменению привязанного элемента, который может быть или не быть элементом в фокусе. Это может запутать пользователей, поэтому мы не рекомендуем так делать.
Резервные стратегии
Для браузеров, которые не поддерживают запросы состояния прокрутки, вы должны предоставить функциональный базовый интерфейс, в котором все элементы будут разборчивыми, даже без выделения "активного".
Распознавание объектов
Вы можете использовать @supports, чтобы улучшить работу только поддерживаемых браузеров:
@supports (container-type: scroll-state) {
/* Enhancement styles here */
}
Резервный код JavaScript
Если выделение важно для удобства пользователей, используйте IntersectionObserver, чтобы определить привязанный элемент. Уменьшите область наблюдения до узкой полосы в центре карусели, указав для rootMargin отрицательное значение. Например, чтобы элемент считался пересекающимся, если он находится в центральных 2% карусели, задайте для rootMargin значение "0px -49%".
// Optional: detect support and apply a JS-based fallback
if (!CSS.supports('container-type', 'scroll-state')) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
// Toggle a class based on intersection
entry.target.classList.toggle('is-snapped', entry.isIntersecting);
});
}, {
root: document.querySelector(".carousel"),
// Carousel item intersects if any part of the carousel item is in the middle 2% of the carousel.
rootMargin: "0px -49%"
});
document.querySelectorAll('.carousel-item').forEach(item => {
observer.observe(item);
});
}