Carrusel de momentos destacados de Snap

Las consultas de contenedor de estado de desplazamiento te permiten aplicar diseño a los elementos según su estado de desplazamiento actual, por ejemplo, si un elemento está "fijo" (a través del posicionamiento fijo) o "ajustado" (a través del ajuste de desplazamiento). Esto habilita experiencias de carrusel o galería en las que el elemento activo se puede distinguir visualmente sin depender de observadores de intersección de JavaScript ni de objetos de escucha de eventos de desplazamiento.

Implementación principal

Para destacar los elementos ajustados, debes establecer un contenedor de ajuste de desplazamiento, definir los objetivos de ajuste como contenedores de estado de desplazamiento y, luego, consultar ese estado para aplicar diseño a los descendientes.

1. Establece el contenedor de ajuste de desplazamiento

El contenedor superior debe tener habilitado 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. Define los objetivos de ajuste como contenedores de estado de desplazamiento

Cada elemento del carrusel del que se deba hacer un seguimiento para la alineación se debe declarar como un contenedor 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. Consulta el estado de snapped

Debido a que las consultas de contenedores aplican diseño a los elementos descendientes, debes aplicar los estilos de destacado a un elemento dentro del objetivo de ajuste. Debido a que el contenedor de desplazamiento está configurado para desbordarse en el eje x, usa la consulta scroll-state(snapped: x).

OBLIGATORIO: Une los estilos en @media (prefers-reduced-motion: no-preference) para mostrar el efecto solo a los usuarios que no solicitaron un movimiento reducido. Según tu caso de uso, puedes conservar partes del efecto, pero, en este caso, las tarjetas parpadean de blanco a azul de una manera que puede causar problemas a algunos usuarios, por lo que lo inhabilitamos por completo.

/* 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;
  }
}

El descriptor snapped puede consultar ejes específicos: x, y, inline, block o both.

Accesibilidad

AVOID usar scroll-state con elementos interactivos.

Los elementos visuales destacados de los elementos ajustados pueden mejorar la UX, pero el elemento ajustado no se expone al árbol de accesibilidad. El tema visual que se aplica a un elemento ajustado no debe indicar que el elemento está activo o enfocado, y el anillo de enfoque del teclado debe ser muy visible y distinto del destacado de snapped. Si el elemento ajustado es interactivo, debes usar otras prácticas de accesibilidad estándar para que sea accesible.

La alineación se produce debido al desplazamiento, que no mueve el enfoque del teclado. Sin embargo, el enfoque del teclado puede hacer que el contenedor de desplazamiento se mueva, lo que provoca un cambio en el elemento ajustado, que puede ser o no el elemento enfocado. Es probable que esto genere confusión en los usuarios, por lo que no se recomienda.

Estrategias de resguardo

En el caso de los navegadores que no admiten consultas de estado de desplazamiento, debes proporcionar una experiencia básica funcional en la que todos los elementos sean legibles, incluso sin el destacado "activo".

Detección de atributos

Puedes usar @supports para proporcionar mejoras solo a los navegadores compatibles:

@supports (container-type: scroll-state) {
  /* Enhancement styles here */
}

Resguardo de JavaScript

Si el elemento destacado es fundamental para la experiencia del usuario, usa IntersectionObserver para determinar el elemento ajustado. Ajusta el área observada a una porción delgada en el centro del carrusel proporcionando un rootMargin con un valor intercalado negativo. Por ejemplo, para considerar que un elemento se interseca si se encuentra en el 2% central del carrusel, establece el atributo rootMargin en "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);
  });
}