Carrousel de Snap Highlights

Les requêtes de conteneur d'état de défilement vous permettent de styliser les éléments en fonction de leur état de défilement actuel, par exemple si un élément est "bloqué" (via un positionnement fixe) ou "ancré" (via un défilement scroll-snap). Cela permet de créer des carrousels ou des galeries où l'élément actif peut être distingué visuellement sans avoir recours à des observateurs d'intersection JavaScript ou à des écouteurs d'événements de défilement.

Implémentation de base

Pour mettre en évidence les éléments alignés, vous devez établir un conteneur scroll-snap, définir les cibles d'alignement comme des conteneurs d'état de défilement, puis interroger cet état pour appliquer un style aux descendants.

1. Établir le conteneur de défilement scroll-snap

Le conteneur parent doit avoir activé 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. Définir les cibles d'alignement comme des conteneurs d'état de défilement

Chaque élément du carrousel qui doit être suivi pour l'alignement doit être déclaré en tant que conteneur 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. Interroger l'état snapped

Étant donné que les requêtes de conteneur appliquent un style aux descendants, vous devez appliquer les styles de mise en surbrillance à un élément à l'intérieur de la cible d'ancrage. Comme le conteneur de défilement est défini pour déborder sur l'axe X, utilisez la requête scroll-state(snapped: x).

OBLIGATOIRE : encapsulez les styles dans @media (prefers-reduced-motion: no-preference) pour n'afficher l'effet qu'aux utilisateurs qui n'ont pas demandé de réduction des animations. Selon votre cas d'utilisation, vous pouvez conserver certaines parties de l'effet, mais dans ce cas, les fiches clignotent du blanc au bleu d'une manière qui peut poser problème à certains utilisateurs. Nous le désactivons donc complètement.

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

Le descripteur snapped peut interroger des axes spécifiques : x, y, inline, block ou both.

Accessibilité

AVOID : utiliser scroll-state avec des éléments interactifs.

Les éléments visuels mis en évidence pour les éléments ancrés peuvent améliorer l'expérience utilisateur, mais l'élément ancré n'est pas exposé à l'arborescence d'accessibilité. Le thème visuel appliqué à un élément ancré ne doit pas indiquer que l'élément est actif ou sélectionné, et un anneau de focus clavier doit être clairement visible et distinct de la mise en surbrillance snapped. Si l'élément ancré est interactif, vous devez utiliser d'autres pratiques d'accessibilité standards pour le rendre accessible.

L'alignement se produit en raison du défilement, qui ne déplace pas le focus clavier. Toutefois, le focus clavier peut entraîner le déplacement du conteneur de défilement, ce qui entraîne une modification de l'élément ancré, qui peut être ou non l'élément sélectionné. Cela risque de prêter à confusion pour les utilisateurs et est donc déconseillé.

Stratégies de remplacement

Pour les navigateurs qui ne sont pas compatibles avec les requêtes d'état de défilement, vous devez fournir une expérience de base fonctionnelle où tous les éléments sont lisibles, même sans la mise en surbrillance "active".

Détection de caractéristiques

Vous pouvez utiliser @supports pour fournir des améliorations uniquement aux navigateurs compatibles :

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

Création de remplacement JavaScript

Si la mise en surbrillance est essentielle pour l'expérience utilisateur, utilisez IntersectionObserver pour déterminer l'élément ancré. Ajustez la zone observée à une fine tranche au centre du carrousel en fournissant un rootMargin avec une valeur intégrée négative. Par exemple, pour qu'un élément soit considéré comme intersectant s'il se trouve dans les 2 % centraux du carrousel, définissez rootMargin sur "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);
  });
}