iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
Formations
JavaScript · Intermédiaire

GSAP

Maîtriser GSAP de zéro : tweens, timelines, ScrollTrigger, plugins et intégration React/Next.js pour créer des animations web professionnelles.

GSAPJavaScriptReactNext.jsScrollTrigger
01Introduction à GSAP02Tweens : to, from, fromTo03Timelines04ScrollTrigger05Stagger, utils et matchMedia06GSAP dans React et Next.js07Plugins et techniques avancées
Chapitre 4·30 min

ScrollTrigger

ScrollTrigger est le plugin qui lie des animations à la position de scroll. Il est gratuit, inclus dans le package npm de GSAP, et c'est l'une des raisons pour lesquelles GSAP domine dans les sites portfolio et marketing modernes.

Setup

import gsap from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
 
gsap.registerPlugin(ScrollTrigger)

Via CDN :

<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/ScrollTrigger.min.js"></script>

L'animation la plus simple

gsap.from('.section', {
  y: 60,
  opacity: 0,
  duration: 0.8,
  scrollTrigger: '.section',  // forme courte : string = sélecteur du trigger
})

Quand .section entre dans le viewport, l'animation se déclenche. C'est suffisant pour 80% des animations d'entrée au scroll.

L'objet scrollTrigger complet

gsap.from('.section', {
  y: 80,
  opacity: 0,
  duration: 1,
  scrollTrigger: {
    trigger: '.section',           // l'élément qui déclenche
    start: 'top 80%',             // quand le TOP du trigger atteint 80% du viewport
    end: 'bottom 20%',            // quand le BOTTOM du trigger atteint 20% du viewport
    toggleActions: 'play none none reverse',
    markers: true,                 // pour déboguer — retirer en production
  },
})

start et end

Le format est "position-du-trigger position-du-viewport".

start: 'top 80%'
        │    │
        │    └── position dans le viewport (depuis le haut)
        └──────── position dans l'élément trigger (depuis le haut)

Valeurs possibles : top, center, bottom, +=100px, 80%

// L'animation démarre quand le centre de l'élément arrive au centre du viewport
start: 'center center'
 
// L'animation démarre quand le bas de l'élément dépasse le haut du viewport
start: 'bottom top'
 
// Utiliser des pixels
start: 'top top+=100'  // quand le top de l'élément est à 100px du haut du viewport

toggleActions

Définit ce qui se passe aux quatre événements de scroll :

toggleActions: 'onEnter onLeave onEnterBack onLeaveBack'

Valeurs disponibles : play, pause, resume, reset, restart, complete, reverse, none

// Joue en entrant, reverse en remontant (le plus naturel)
toggleActions: 'play none none reverse'
 
// Joue en entrant, reset (revient au début) en remontant
toggleActions: 'play none none reset'
 
// Ne joue qu'une fois
toggleActions: 'play none none none'
 
// Redémarre à chaque entrée
toggleActions: 'restart none none none'

Scrub — animation synchronisée au scroll

scrub lie l'animation à la position exacte du scroll. L'animation avance quand on scroll vers le bas, recule quand on remonte.

gsap.to('.hero-image', {
  y: 200,
  scrollTrigger: {
    trigger: '.hero',
    start: 'top top',
    end: 'bottom top',
    scrub: true,    // synchronisation directe
  },
})

scrub: true = synchronisation immédiate. scrub: 1 = lag de 1 seconde — l'animation "rattrape" le scroll avec un délai, effet plus doux.

scrollTrigger: {
  scrub: 1,     // lag smoothing de 1 seconde
}

Effet parallax

// Image qui monte plus lentement que le scroll
gsap.to('.parallax-bg', {
  y: '-30%',
  ease: 'none',  // toujours none avec scrub
  scrollTrigger: {
    trigger: '.parallax-section',
    start: 'top bottom',
    end: 'bottom top',
    scrub: true,
  },
})

Avec scrub, utilisez toujours ease: 'none' — l'ease est géré par le scrub lui-même.

Pin — épingler un élément pendant le scroll

pin: true fixe l'élément à l'écran pendant que la page défile, puis le libère à la fin.

ScrollTrigger.create({
  trigger: '.sticky-section',
  start: 'top top',
  end: '+=500',    // reste épinglé pendant 500px de scroll
  pin: true,
})

Combiné avec scrub, c'est le pattern classique des sections "fullscreen scroll" :

const tl = gsap.timeline()
 
tl
  .from('.slide-1', { opacity: 0 })
  .from('.slide-2', { opacity: 0 })
  .from('.slide-3', { opacity: 0 })
 
ScrollTrigger.create({
  trigger: '.scroll-container',
  start: 'top top',
  end: '+=2000',
  pin: true,
  scrub: 1,
  animation: tl,
})

Ici, la timeline s'étale sur 2000px de scroll. L'élément est épinglé, la timeline progresse au rythme du scroll.

Lier une timeline entière à ScrollTrigger

const tl = gsap.timeline({
  scrollTrigger: {
    trigger: '.section',
    start: 'top 70%',
    end: 'bottom 30%',
    toggleActions: 'play none none reverse',
  },
})
 
tl
  .from('.section-title', { y: 30, opacity: 0, duration: 0.6 })
  .from('.section-text', { y: 20, opacity: 0, duration: 0.5 }, '-=0.2')
  .from('.section-image', { scale: 1.05, opacity: 0, duration: 0.7 }, '<')

Toute la séquence se déclenche quand le trigger entre dans le viewport.

ScrollTrigger.create()

Quand vous voulez un ScrollTrigger sans animation attachée — juste pour ajouter/retirer des classes CSS, par exemple.

ScrollTrigger.create({
  trigger: '.nav',
  start: 'top top',
  onEnter: () => document.querySelector('.nav').classList.add('scrolled'),
  onLeaveBack: () => document.querySelector('.nav').classList.remove('scrolled'),
})

Ou pour déclencher n'importe quelle logique au scroll :

ScrollTrigger.create({
  trigger: '#section-3',
  start: 'top center',
  onEnter: () => {
    // Charger du contenu, envoyer un event analytics, etc.
    trackEvent('section-3-viewed')
  },
  once: true,  // ne se déclenche qu'une fois
})

Les marqueurs de debug

scrollTrigger: {
  trigger: '.section',
  start: 'top 80%',
  markers: true,  // affiche les lignes de debug dans le navigateur
}

markers: true affiche des lignes colorées dans le navigateur qui montrent exactement où le trigger et les points start/end se trouvent. Indispensable pour déboguer. Toujours retirer en production.

Refresh et resize

ScrollTrigger recalcule automatiquement les positions au resize de la fenêtre. Mais si le contenu change dynamiquement (images qui chargent, accordéons qui s'ouvrent), il peut perdre le calcul.

// Forcer un recalcul
ScrollTrigger.refresh()
 
// S'assurer que les images sont chargées avant de calculer
window.addEventListener('load', () => {
  ScrollTrigger.refresh()
})

En React/Next.js avec des images next/image, attendez que les images soient chargées ou appelez ScrollTrigger.refresh() dans un useEffect après le rendu.

Batch — animer des listes au scroll

Pour animer une liste d'éléments qui entrent au scroll un par un, ScrollTrigger.batch() est plus efficace que de créer un ScrollTrigger par élément.

ScrollTrigger.batch('.card', {
  onEnter: (elements) => {
    gsap.from(elements, {
      y: 40,
      opacity: 0,
      stagger: 0.1,
      duration: 0.6,
    })
  },
  onLeave: (elements) => {
    gsap.to(elements, { opacity: 0, duration: 0.3 })
  },
  onEnterBack: (elements) => {
    gsap.to(elements, { opacity: 1, y: 0, duration: 0.3 })
  },
  start: 'top 85%',
})

batch() regroupe les éléments qui entrent dans le viewport à intervalles proches (par défaut 0.1s) et les passe ensemble au callback.


ScrollTrigger couvre la majorité des besoins d'animations au scroll. Le prochain chapitre explore les techniques avancées : stagger, gsap.utils, et matchMedia pour les animations responsive.

Précédent
Timelines
Suivant
Stagger, utils et matchMedia

Développeur fullstack passionné. J'apprends en construisant et je documente tout — front, back, outils. Le code s'apprend mieux en public.

Naviguer

IndexTous les articlesFormationsProfilOutilsBibliothech

Ailleurs

GitHub RSS

Newsletter

Les articles, libs et découvertes. Une fois par semaine, pas plus.

© 2026 William LoreeConçu & codé à la main