Créer des effets de diapositive de carrousel

Les effets de transition sont un excellent moyen d'ajouter un intérêt visuel à un carrousel. Lorsque l'utilisateur fait défiler les diapositives, chacune d'elles peut s'animer lorsqu'elle entre, se centre et sort de la zone de défilement. Par exemple, les diapositives peuvent apparaître et disparaître en fondu, pivoter ou changer de taille. Cela crée une expérience utilisateur dynamique et engageante. Contrairement aux animations d'entrée/sortie simples, cet effet utilise une seule animation continue pour contrôler l'apparence de la diapositive sur l'ensemble du port de défilement.

Mise en œuvre

Pour créer des effets de diapositive de carrousel :

  1. Créer un scroller : cet élément servira de conteneur pour les diapositives de votre carrousel. Dans cet exemple, il utilise overflow-x: scroll pour permettre le défilement horizontal.

    <ul class="scroller">
      <li class="entry">1</li>
      <li class="entry">2</li>
      <li class="entry">3</li>
      …
    </ul>
    
    .scroller {
      overflow-x: scroll;
    }
    
  2. Définir l'animation : créez une animation CSS qui définit les différents états de vos diapositives lorsqu'elles traversent la fenêtre de défilement. Vous pouvez définir des images clés pour n'importe quelle partie de l'animation. Par exemple, vous pouvez définir un état lorsque les diapositives se trouvent au centre de la zone de défilement en incluant une image clé 50%. Dans cet exemple, la propriété scale agrandit les diapositives à mesure qu'elles s'approchent du centre et les réduit à mesure qu'elles s'en éloignent.

    @keyframes animate {
      0% {
        scale: 0.5;
      }
      50% {
        scale: 1;
      }
      100% {
        scale: 0.5;
      }
    }
    
  3. Appliquez l'animation et view-timeline : associez l'animation aux diapositives du carrousel et associez-la à un view-timeline qui suit l'élément lorsqu'il défile dans le conteneur.

    .scroller > * {
      animation: animate auto linear both;
      animation-timeline: view(inline);
    }
    

    Par défaut, view() suit l'élément sur l'axe block. Si vous devez le suivre sur l'axe inline, vous pouvez utiliser view(inline).

Exemple de code

Ce code anime les éléments d'un carrousel à défilement horizontal à l'aide d'une chronologie d'affichage anonyme :

@keyframes animate {
  0% {
    scale: 0.5;
  }

  50% {
    scale: 1;
  }

  100% {
    scale: 0.5;
  }
}

.scroller > * {
  /* Applies the animation using an `auto` duration */
  animation: animate auto linear both;
  /* Sets the animation timeline to use an anonymous view progress timeline, tracking the element's progress through the scroller on the inline axis */
  animation-timeline: view(inline);
}

Ce code anime les éléments d'un carrousel à défilement horizontal à l'aide d'une chronologie d'affichage nommée :

@keyframes animate {
  0% {
    scale: 0.5;
  }

  50% {
    scale: 1;
  }

  100% {
    scale: 0.5;
  }
}

/* This creates a named view-timeline on each carousel item. The timeline is used to drive the animation that is applied on the same element. */
.scroller > * {
  /* Applies the animation using an `auto` duration */
  animation: animate auto linear both;
  /* Defines a named view progress timeline, tracking the element's progress through the scroller on the inline axis */
  view-timeline: --item inline;
  /* Sets the animation timeline to use the named view progress timeline defined above */
  animation-timeline: --item;
}

Bonnes pratiques

Lorsque vous utilisez des animations basées sur le défilement, il est important de suivre quelques bonnes pratiques pour garantir une expérience fluide et accessible :

  • À FAIRE : incluez la détection de fonctionnalités. Tous les navigateurs ne sont pas compatibles avec les animations déclenchées par le défilement. Utilisez @supports ((animation-timeline: view()) and (animation-range: entry)) pour vérifier la compatibilité et fournir une solution de remplacement pour les navigateurs qui ne sont pas compatibles.
    • La vérification (animation-range: entry) DOIT être incluse ici pour filtrer les navigateurs qui ne sont que partiellement compatibles.
    • N'UTILISEZ PAS le package scroll-timeline-polyfill pour la stratégie de remplacement, car il n'est pas complet et présente de nombreux problèmes connus.
    • Si l'animation n'est considérée que comme décorative, optez pour l'amélioration progressive et NE FOURNISSEZ PAS de solution de remplacement.
  • À FAIRE : Respectez les préférences des utilisateurs. Certains préfèrent avoir moins de mouvements sur le Web. Utilisez la requête média prefers-reduced-motion pour désactiver ou réduire vos animations pour ces utilisateurs.
  • À FAIRE : Essayez de n'animer que les propriétés CSS performantes. Pour des animations plus fluides, n'animez que les propriétés pouvant être gérées par le thread du compositeur du navigateur, telles que transform et opacity. L'animation d'autres propriétés telles que width ou height peut entraîner des problèmes de performances.
  • À FAIRE : Utilisez l'ordre de déclaration correct. Lorsque vous utilisez la propriété abrégée animation, déclarez animation-timeline et animation-range après pour éviter que la propriété abrégée ne réinitialise la chronologie.

Privilégiez un view-timeline nommé lorsque plusieurs éléments DOM doivent être animés en fonction de la même chronologie, ou lorsque vous devez animer les enfants de l'élément sur lequel le view-timeline est défini. Si l'élément que vous animez est également celui qui définit la view-timeline, vous pouvez utiliser une chronologie d'affichage anonyme à l'aide de view().

Lorsque vous utilisez la fonction view() pour créer une animation basée sur le défilement :

  • FACULTATIF : indiquez explicitement l'axe à suivre. Lorsque vous ne ciblez pas l'axe block par défaut (par exemple, dans un défilement horizontal), indiquez explicitement l'axe à suivre avec view(block) ou view(inline).

Lorsque vous utilisez la propriété view-timeline pour créer une animation basée sur le défilement :

  • À FAIRE : Utilisez un <dashed-ident> CSS pour le nom (par exemple, view-timeline: --my-custom-name).
  • FACULTATIF : Soyez explicite sur l'axe à suivre. Lorsque vous ne ciblez pas l'axe block par défaut (par exemple, dans un défilement horizontal), indiquez explicitement l'axe à suivre avec view-timeline-axis.
  • À FAIRE : Assurez-vous que le champ d'application de la recherche fonctionne. Lorsque l'élément qui déclare view-timeline n'est pas un ancêtre de l'arborescence plate de l'élément animé, augmentez la visibilité du nom de view-timeline en utilisant timeline-scope sur un ancêtre partagé.

Compatibilité des navigateurs et stratégies de remplacement

Par conséquent, une stratégie de remplacement est généralement requise.

Pour les navigateurs qui ne sont pas compatibles avec les animations déclenchées par le défilement, vous pouvez utiliser une solution de remplacement pour recréer les effets visuels. Les solutions de remplacement sont généralement créées avec un écouteur de défilement (pour les effets ScrollTimeline) ou l'API IntersectionObserver (pour les effets ViewTimeline).

Dans les navigateurs compatibles de manière native avec les animations liées au défilement, utilisez TOUJOURS l'implémentation CSS native, car elle est plus performante.

Notez que tous les effets ne peuvent pas être recréés à l'aide de l'approche de remplacement.

Pour ce cas d'utilisation spécifique, le script suivant applique la solution de remplacement pour les navigateurs qui ne sont pas compatibles avec les animations déclenchées par le défilement. Il utilise l'API Web Animations (Element.animate()) pour créer une animation en pause pour chaque élément du carrousel. Il écoute ensuite l'événement scroll sur le défileur et met à jour la currentTime de chaque animation en fonction de la progression du défilement de l'élément dans le défileur.

// Fallback for browsers that don't support scroll-driven animations
if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
  const scroller = document.querySelector('.scroller');
  const entries = document.querySelectorAll('.entry');

  // Create a map to store animations
  const animations = new Map();

  entries.forEach(entry => {
    const animation = entry.animate(
      {
        scale: ['0.5', '1', '0.5']
      },
      {
        duration: 1, // We'll control the time ourselves
        fill: 'both'
      }
    );
    animation.pause();
    animations.set(entry, animation);
  });

  // Update animations on scroll
  const tick = () => {
    const scrollerRect = scroller.getBoundingClientRect();

    entries.forEach(entry => {
      const animation = animations.get(entry);
      if (!animation) return;

      const entryRect = entry.getBoundingClientRect();
      const progress = (entryRect.left + entryRect.width / 2 - scrollerRect.left) / scrollerRect.width;

      animation.currentTime = progress;
    });
  };
    
  scroller.addEventListener('scroll', tick);
  tick();
}