Maîtriser GSAP de zéro : tweens, timelines, ScrollTrigger, plugins et intégration React/Next.js pour créer des animations web professionnelles.
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.
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>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.
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
},
})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 viewportDé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 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
}// 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: 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.
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.
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
})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.
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.
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.