En bref

  • Dans l’App Router, le contenu reste dans un Server Component et seul un petit Client Component, qui reçoit ce contenu en children, lance les animations.
  • gsap.matchMedia(), apparu avec GSAP 3.11, regroupe le nettoyage des animations et la condition prefers-reduced-motion. Avec le mouvement réduit, rien ne démarre et le contenu reste affiché.
  • Charger GSAP à la demande ou l’importer directement dépend du premier écran. Animé en CSS, il permet de différer la librairie ; animé par GSAP dès le chargement, il en a besoin tout de suite.

GSAP est présent sur la plupart des projets React et Next.js sur lesquels j’ai travaillé. Sur TED et Aliz, deux applications React rendues entièrement dans le navigateur, nous l’utilisions pour animer le site vitrine de l’une et les landing pages de l’autre. Je l’utilise aujourd’hui sur ce portfolio et sur Les Chemins de la Maison, deux sites construits avec l’App Router de Next.js, où le rendu côté serveur change la façon de l’intégrer.

Quand GSAP plutôt que CSS ?

Pour un changement d’état ou un survol, une transition CSS suffit et n’ajoute pas de JavaScript. GSAP devient utile quand il faut enchaîner plusieurs animations dans une timeline, les lier au scroll avec ScrollTrigger ou suivre une valeur continue comme la position du pointeur.

La frontière ne se place pas toujours là où on l’attend. Sur Les Chemins de la Maison, même l’entrée du premier écran est écrite en CSS, avec des keyframes sur l’opacité et le transform, pour qu’elle démarre sans attendre le JavaScript. GSAP ne prend le relais qu’ensuite, notamment pour les éléments qui apparaissent au scroll.

Qu’est-ce qui change entre une SPA React et l’App Router ?

Sur TED, nous avions regroupé les animations dans des helpers partagés, comme revealText, qui fait glisser un texte en fondu, ou revealImage, qui retire un overlay posé sur une image. Tout le rendu se faisait dans le navigateur, et chaque composant appelait ces fonctions sur ses propres éléments.

Dans l’App Router, les composants sont des Server Components par défaut, alors que GSAP, qui manipule le DOM, demande un Client Component. Mettre « use client » sur toute une section ferait passer tout son contenu côté client. Je garde donc la frontière la plus basse possible : la section reste un Server Component, et un petit composant client, RevealSection, reçoit son contenu en children pour l’animer.

// Hero.tsx (simplified): a Server Component. Only RevealSection runs in the browser. const revealSteps: RevealStep[] = [ {target: 'badge', from: {opacity: 0, y: 14, duration: 0.5}}, {target: 'heading', from: {y: 32, duration: 0.7}, position: '-=0.2'}, {target: 'tagline', from: {opacity: 0, y: 18, duration: 0.55}, position: '-=0.4'}, ]; export default function Hero() { return ( <RevealSection steps={revealSteps} playOn="load"> <h1 data-gsap="heading">…</h1> {/* … */} </RevealSection> ); } 

Les étapes passent d’un Server Component à un Client Component, et doivent donc être sérialisables. RevealSection n’accepte qu’un sous-ensemble des propriétés de GSAP (opacity, x, y, duration, stagger, ease), sans fonction de rappel. Elles sont aussi déclarées au niveau du module, car un tableau recréé à chaque rendu relancerait l’animation. Les Chemins de la Maison suit le même principe avec RevealGrid, qui anime les cartes d’une grille rendue côté serveur.

Comment nettoyer les animations et respecter le mouvement réduit ?

En développement, le Strict Mode de React monte, démonte puis remonte les composants, et un effet qui ne nettoie pas ses animations peut alors lancer deux fois la même. Depuis la version 3.11, plus récente que celle de TED, GSAP fournit gsap.context() et gsap.matchMedia(), qui enregistrent les animations créées dans leur callback pour les annuler toutes d’un seul revert().

// Simplified example useEffect(() => { const mm = gsap.matchMedia(); mm.add('(prefers-reduced-motion: no-preference)', () => { const tl = gsap.timeline({ scrollTrigger: {trigger: sectionRef.current, start: 'top 78%'}, }); steps.forEach(({target, from, position}) => { tl.from(`[data-gsap="${target}"]`, from, position); }); }, sectionRef); // selectors only match inside this section return () => mm.revert(); }, [steps]); 

La condition passée à mm.add() sert aussi à respecter prefers-reduced-motion, le réglage par lequel certaines personnes demandent à leur système moins d’animations, parce que certains mouvements peuvent provoquer des vertiges ou gêner la lecture. Avec ce réglage, aucune animation n’est créée, et le contenu, déjà à sa place dans le HTML, reste simplement affiché : la frise du parcours apparaît entièrement dessinée, avec toutes ses étapes. La même mécanique accepte d’autres conditions. L’effet qui suit le pointeur dans le Hero n’est ainsi créé qu’avec (pointer: fine) and (min-width: 1024px), c’est-à-dire sur un écran large utilisé avec une souris ou un pavé tactile.

Le hook useGSAP de @gsap/react assure le même nettoyage avec gsap.context(), mais matchMedia garde ici ces conditions au même endroit que les animations. L’article sur l’accessibilité montre le même réglage appliqué à une vidéo d’accueil et aux animations Framer Motion.

Comment animer sans retarder le premier affichage ?

Les deux sites ne chargent pas GSAP au même moment. Sur Les Chemins de la Maison, la librairie n’est importée qu’après le premier affichage, ce qui retire environ 44 Ko compressés du JavaScript initial de chaque page, comme le détaille l’article sur ce site vitrine. C’est possible parce que son premier écran est animé en CSS. Sur ce portfolio, le Hero s’anime avec GSAP dès le chargement, et la librairie est donc importée directement.

Dans les deux cas, le contenu occupe sa place dès le HTML et l’animation ne touche qu’à son apparence, avec transform et opacity, ce qui évite les décalages de mise en page. La frise de ce portfolio se dessine ainsi avec un scaleY plutôt qu’en animant sa hauteur. Sur Les Chemins de la Maison, un élément déjà visible au moment où GSAP arrive n’est jamais masqué pour être révélé ensuite, et l’image principale de la page n’apparaît jamais en fondu.

Lighthouse donne à ce portfolio 99 en performance sur mobile et 100 dans les autres catégories. Les Chemins de la Maison obtient 100 sur desktop et 94 sur mobile dans PageSpeed, mesurés le 15 octobre. GSAP ajoute du JavaScript, et ces scores montrent seulement que son usage n’empêche pas les pages de s’afficher vite.

Entre les applications React de TED ou d’Aliz et l’App Router, GSAP sert toujours aux mêmes animations, mais sa place dans le code a changé. Le contenu reste rendu côté serveur, un petit composant client l’anime, et gsap.matchMedia() regroupe le nettoyage et le mouvement réduit. Le moment où charger la librairie dépend, lui, de ce que le premier écran doit animer.

Pour voir le résultat, parcourez l’accueil de ce site ; pour en discuter, écrivez-moi.

Sources

Publié à l’origine sur jordan-philippot.fr.