As consultas de contêiner de estado de rolagem permitem estilizar elementos com base no estado de rolagem atual, como se um elemento está "preso" (com posicionamento fixo) ou "ajustado" (com ajuste de rolagem). Isso permite experiências de carrossel ou galeria em que o item ativo pode ser distinguido visualmente sem depender de observadores de interseção JavaScript ou listeners de eventos de rolagem.
Implementação principal
Para destacar itens ajustados, é necessário estabelecer um contêiner de ajuste de rolagem, definir os destinos de ajuste como contêineres de estado de rolagem e, em seguida, consultar esse estado para estilizar os descendentes.
1. Estabelecer o contêiner de ajuste de rolagem
O contêiner pai precisa ter o scroll-snap-type ativado.
<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. Definir destinos de ajuste como contêineres de estado de rolagem
Cada item no carrossel que precisa ser rastreado para ajuste precisa ser declarado como um contêiner 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. Consultar o estado snapped
Como as consultas de contêiner estilizam os descendentes, é necessário aplicar os estilos de destaque a um elemento dentro do destino de ajuste. Como o contêiner de rolagem está definido para transbordar no eixo x, use a consulta scroll-state(snapped: x).
OBRIGATÓRIO: envolva os estilos em @media (prefers-reduced-motion: no-preference) para mostrar o efeito apenas aos usuários que não solicitaram a redução de movimento. Dependendo do seu caso de uso, você pode manter partes do efeito, mas, neste caso, os cards piscam de branco para azul de uma forma que pode causar problemas para alguns usuários. Por isso, desativamos o efeito completamente.
/* 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;
}
}
O descritor snapped pode consultar eixos específicos: x, y, inline, block ou both.
Acessibilidade
AVOID: usar scroll-state com elementos interativos.
Os destaques visuais para itens fixados podem melhorar a UX, mas o item fixado não é exposto à árvore de acessibilidade. O tema visual aplicado a um item fixado não pode indicar que o elemento está ativo ou em foco, e um anel de foco do teclado precisa ser bem visível e diferente do destaque de snapped. Se o item fixado for interativo, use outras práticas padrão de acessibilidade para torná-lo acessível.
A fixação ocorre devido à rolagem, que não move o foco do teclado. No entanto, o foco do teclado pode fazer com que o contêiner de rolagem se mova, causando uma mudança no item fixado, que pode ou não ser o item em foco. Isso pode confundir os usuários e não é recomendado.
Estratégias de fallback
Para navegadores que não oferecem suporte a consultas de estado de rolagem, forneça uma experiência básica funcional em que todos os itens sejam legíveis, mesmo sem o destaque "ativo".
Detecção de recursos
Você pode usar @supports para fornecer melhorias apenas para navegadores compatíveis:
@supports (container-type: scroll-state) {
/* Enhancement styles here */
}
Substituto do JavaScript
Se o destaque for essencial para a experiência do usuário, use IntersectionObserver para determinar o item fixado. Ajuste a área observada para uma fatia fina no centro do carrossel fornecendo um rootMargin com um valor inline negativo. Por exemplo, para considerar que um elemento está em interseção se estiver nos 2% centrais do carrossel, defina rootMargin como "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);
});
}