aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Frontend & UI

Animation au Scroll : Effets Performants avec Intersection Observer

Une bonne animation au scroll fait apparaître les éléments au moment précis où l'on descend dans la page — ni trop tôt, ni trop tard. Pendant des années, nous faisions cela en écoutant l'événement scroll et en calculant chaque image à la main. Le navigateur peut désormais s'en charger sans surcharger le thread principal : IntersectionObserver. Dans ce guide, je détaille comment détecter efficacement l'entrée d'un élément dans le viewport, comment l'associer au CSS pour des transitions fluides, et comment éviter les pièges habituels de performance et d'accessibilité.

Pourquoi Intersection Observer plutôt que l'événement scroll ?

L'approche classique utilisait window.addEventListener('scroll', ...) et appelait getBoundingClientRect() pour chaque élément à chaque pas de défilement. Deux problèmes : l'événement scroll se déclenche des dizaines de fois par seconde, et getBoundingClientRect() force le navigateur à recalculer la mise en page (reflow). Ensemble, ils saturent le thread principal et le défilement saccade.

Intersection Observer fonctionne à l'inverse : vous dites au navigateur « préviens-moi quand cet élément entre dans la vue », et il le surveille en interne, de façon asynchrone et optimisée. Le callback ne s'exécute que lorsque l'état d'intersection change, pas à chaque image de défilement. Résultat : moins de code, moins de reflow, une page plus fluide.

Mettre en place son premier observateur

L'usage de base comporte trois parties : les éléments cibles à observer, un callback exécuté à l'intersection, et des options qui règlent le comportement.

const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      entry.target.classList.add('is-visible');
      // arrête de surveiller une fois apparu
      observer.unobserve(entry.target);
    }
  });
}, {
  threshold: 0.2,        // déclenche quand 20% de l'élément est visible
  rootMargin: '0px 0px -10% 0px'
});

document.querySelectorAll('.reveal').forEach((el) => {
  observer.observe(el);
});

L'astuce clé ici : JavaScript ne fait qu'ajouter une classe CSS (is-visible). C'est le CSS qui réalise l'animation. Cette séparation est essentielle, tant pour la performance que pour la maintenance.

Laisser l'animation au CSS

On définit les états de départ et d'arrivée de l'élément en CSS et on adoucit la transition entre eux avec transition. Utiliser transform et opacity est important : ces deux propriétés sont traitées sur le GPU et ne déclenchent pas de recalcul de mise en page.

.reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

Avec la même structure, vous pouvez produire des variantes comme l'arrivée depuis la gauche, la mise à l'échelle ou une légère rotation via différentes classes. Autre avantage de garder l'animation en CSS : même si JavaScript ne s'exécute pas, le contenu ne doit pas rester masqué à opacity: 0 — nous sécuriserons cela dans la section accessibilité.

Bien régler threshold et rootMargin

Ces deux options définissent entièrement le ressenti d'une animation au scroll :

  • threshold : un ratio entre 0 et 1. 0 déclenche dès qu'un seul pixel est visible, 1 quand l'élément l'est entièrement. Vous pouvez aussi passer un tableau ([0, 0.25, 0.5, 1]) et recevoir le callback à chaque seuil.
  • rootMargin : agrandit ou réduit la boîte racine ; il utilise la syntaxe margin du CSS. Une valeur inférieure négative comme -10% retarde le déclenchement jusqu'à ce que l'élément soit un peu plus dans l'écran, pour une animation « au bon moment ».
  • root : par défaut le viewport (null). Si vous observez dans un conteneur défilable, passez cet élément comme root.

Effet d'apparition échelonné (stagger)

Des cartes qui apparaissent l'une après l'autre avec un léger décalage sont très appréciées dans les interfaces modernes. On peut le faire proprement en assignant un délai à chaque élément via une variable CSS :

const items = document.querySelectorAll('.reveal');
const io = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (!entry.isIntersecting) return;
    const i = Number(entry.target.dataset.index || 0);
    entry.target.style.setProperty('--delay', `${i * 80}ms`);
    entry.target.classList.add('is-visible');
    io.unobserve(entry.target);
  });
}, { threshold: 0.15 });

items.forEach((el, i) => {
  el.dataset.index = i;
  io.observe(el);
});

Côté CSS, vous reliez ce délai à la transition : transition-delay: var(--delay, 0ms);. Quatre cartes apparaissent alors en séquence, en une vague fluide.

Pièges de performance et d'accessibilité

Quelques détails rendent le projet professionnel :

  • Gérer le re-déclenchement : une fois l'élément apparu, cessez de le surveiller avec unobserve() ; sinon l'animation se réinitialise quand l'utilisateur remonte. Si vous voulez qu'elle rejoue, retirez la classe is-visible lorsque !entry.isIntersecting.
  • Préférence de réduction de mouvement : certains utilisateurs sont gênés par le mouvement. Utilisez @media (prefers-reduced-motion: reduce) pour désactiver les transitions et afficher le contenu directement.
  • Ne pas masquer le contenu si JavaScript est désactivé : appliquez l'état masqué initial uniquement quand JS est actif. Une approche pratique : ajouter document.documentElement.classList.add('js') à l'élément <html> et conditionner le CSS en .js .reveal { opacity: 0; }.
@media (prefers-reduced-motion: reduce) {
  .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

Questions fréquentes

Quel est le support navigateur d'Intersection Observer ?

Il est pris en charge depuis des années par tous les navigateurs actuels (Chrome, Firefox, Safari, Edge) ; aucune bibliothèque externe nécessaire. Sauf si vous visez de très vieux navigateurs, vous n'aurez pas besoin de polyfill non plus.

Écouter le scroll avec requestAnimationFrame est-il mieux ?

Pour des effets continus qui évoluent tout au long du défilement, comme le parallax, requestAnimationFrame est adapté. Mais pour savoir si un élément est visible, Intersection Observer reste toujours la solution la plus efficace et la plus simple.

L'animation doit-elle rejouer à chaque défilement ?

En général non. Laissez le contenu apparaître une fois et rester ; des animations d'entrée qui se répètent sans cesse distraient et pèsent sur la performance. Dans de rares cas vous pouvez les rejouer, mais unobserve() est la valeur par défaut recommandée.

Vous voulez des animations au scroll fluides et performantes sur votre site ? Pour des effets d'interface accessibles, sans saccades et réglés avec les bons seuils, contactez-moi.

Bu kategorideki tüm yazılar →

Devamı için