GSAP avec Vue, Svelte et autres Frameworks
Quand utiliser cette compétence
Appliquez-la lors de l'écriture ou de la révision de code GSAP dans Vue (ou Nuxt), Svelte (ou SvelteKit), ou d'autres frameworks à composants qui utilisent un cycle de vie (monté/démonté). Pour React, utilisez spécifiquement gsap-react (hook useGSAP, gsap.context()).
Compétences associées : Pour les tweens et timelines, utilisez gsap-core et gsap-timeline ; pour les animations basées sur le scroll, utilisez gsap-scrolltrigger ; pour React, utilisez gsap-react.
Principes (Tous les frameworks)
- Créez les tweens et ScrollTriggers après que le DOM du composant soit disponible (ex. onMounted, onMount).
- Détruisez ou annulez les animations lors du démontage (ou équivalent) pour éviter qu'elles ne s'exécutent sur des nœuds détachés et pour éviter les fuites mémoire.
- Limitez la portée des sélecteurs à la racine du composant afin que
.boxet autres ne ciblent que les éléments à l'intérieur de ce composant, pas le reste de la page.
Vue 3 (Composition API)
Voir examples/vue/ pour un projet Vite + Vue 3 fonctionnel démontrant ces motifs.
Utilisez onMounted pour exécuter GSAP après que le composant soit dans le DOM. Utilisez onUnmounted pour le nettoyage.
import { onMounted, onUnmounted, ref } from "vue";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger); // une seule fois par application, ex. dans main.js
export default {
setup() {
const container = ref(null);
let ctx;
onMounted(() => {
if (!container.value) return;
ctx = gsap.context(() => {
gsap.to(".box", { x: 100, duration: 0.6 });
gsap.from(".item", { autoAlpha: 0, y: 20, stagger: 0.1 });
}, container.value);
});
onUnmounted(() => {
ctx?.revert();
});
return { container };
},
};
- ✅ gsap.context(scope) — passez la référence du conteneur (ex.
container.value) comme deuxième argument pour que les sélecteurs comme.itemsoient limités à cette racine. Toutes les animations et ScrollTriggers créés à l'intérieur du callback sont suivis et annulés lorsque ctx.revert() est appelé. - ✅ onUnmounted — appelez toujours ctx.revert() pour détruire les tweens et ScrollTriggers et annuler les styles en ligne.
Vue 3 (script setup)
Même idée avec <script setup> et refs :
<script setup>
import { onMounted, onUnmounted, ref } from "vue";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
const container = ref(null);
let ctx;
onMounted(() => {
if (!container.value) return;
ctx = gsap.context(() => {
gsap.to(".box", { x: 100 });
gsap.from(".item", { autoAlpha: 0, stagger: 0.1 });
}, container.value);
});
onUnmounted(() => {
ctx?.revert();
});
</script>
<template>
<div ref="container">
<div class="box">Boîte</div>
<div class="item">Objet</div>
</div>
</template>
Nuxt 4
Voir
examples/nuxt/pour un projet Nuxt 4 fonctionnel avec enregistrement de plugin, chargement différé et motifs compatibles SSR.
Utilisez un composable réutilisable pour enregistrer les plugins GSAP et également charger différé les plugins qui ne sont pas largement utilisés dans votre application :
// composables/useGSAP.ts
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
const PLUGINS = [
"CSSRulePlugin",
"CustomBounce",
"CustomEase",
"CustomWiggle",
"Draggable",
"DrawSVGPlugin",
"EaselPlugin",
"EasePack",
"Flip",
"GSDevTools",
"InertiaPlugin",
"MorphSVGPlugin",
"MotionPathHelper",
"MotionPathPlugin",
"Observer",
"Physics2DPlugin",
"PhysicsPropsPlugin",
"PixiPlugin",
"ScrambleTextPlugin",
"ScrollSmoother",
"ScrollToPlugin",
"ScrollTrigger",
"SplitText",
"TextPlugin",
] as const;
type Plugins = (typeof PLUGINS)[number];
// Afin de charger dynamiquement tous les plugins GSAP
const pluginMap = {
CustomEase: () => import("gsap/CustomEase"),
Draggable: () => import("gsap/Draggable"),
CSSRulePlugin: () => import("gsap/CSSRulePlugin"),
EaselPlugin: () => import("gsap/EaselPlugin"),
EasePack: () => import("gsap/EasePack"),
Flip: () => import("gsap/Flip"),
MotionPathPlugin: () => import("gsap/MotionPathPlugin"),
Observer: () => import("gsap/Observer"),
PixiPlugin: () => import("gsap/PixiPlugin"),
ScrollToPlugin: () => import("gsap/ScrollToPlugin"),
ScrollTrigger: () => import("gsap/ScrollTrigger"),
TextPlugin: () => import("gsap/TextPlugin"),
DrawSVGPlugin: () => import("gsap/DrawSVGPlugin"),
Physics2DPlugin: () => import("gsap/Physics2DPlugin"),
PhysicsPropsPlugin: () => import("gsap/PhysicsPropsPlugin"),
ScrambleTextPlugin: () => import("gsap/ScrambleTextPlugin"),
CustomBounce: () => import("gsap/CustomBounce"),
CustomWiggle: () => import("gsap/CustomWiggle"),
GSDevTools: () => import("gsap/GSDevTools"),
InertiaPlugin: () => import("gsap/InertiaPlugin"),
MorphSVGPlugin: () => import("gsap/MorphSVGPlugin"),
MotionPathHelper: () => import("gsap/MotionPathHelper"),
ScrollSmoother: () => import("gsap/ScrollSmoother"),
SplitText: () => import("gsap/SplitText"),
} as const;
type PluginMap = typeof pluginMap;
type Plugins = keyof PluginMap;
// Résout le type du module pour une clé donnée, puis sélectionne l'export nommé correspondant à la clé
// cela permet d'avoir les définitions de type pour l'autocomplétion dans votre éditeur de code
type PluginModule<K extends Plugins> = Awaited<ReturnType<PluginMap[K]>>;
type PluginExport<K extends Plugins> = PluginModule<K>[K & keyof PluginModule<K>];
export default function () {
// Enregistrez tous les plugins GSAP que vous souhaitez à ce stade
gsap.registerPlugin(ScrollTrigger);
/*
Si vous souhaitez charger certains plugins qui ne sont pas largement utilisés
dans votre application (par exemple dans seulement quelques composants ou une seule route),
vous pouvez utiliser cette méthode
*/
async function lazyLoadPlugin<K extends Plugins>(plugin: K): Promise<PluginExport<K>> {
const loader = pluginMap[plugin];
const m = await loader();
const p = (m as any)[plugin];
gsap.registerPlugin(p);
return p;
}
return {
gsap,
ScrollTrigger,
lazyLoadPlugin,
};
}
Accédez-y dans les composants via useGSAP() :
const { gsap, ScrollTrigger, lazyLoadPlugin } = useGSAP();
- ✅
useGSAP()fournit un accès typé à l'instance gsap et à la méthode de chargement différé. - ✅ Chargez différé n'importe quel plugin (SplitText, MorphSVG, etc.) qui n'est pas largement utilisé dans votre application pour réduire la taille initiale du bundle.
- ✅ Utilisez gsap.context(scope) et onUnmounted → ctx.revert() dans les composants, comme pour Vue 3.
Svelte
Utilisez onMount pour exécuter GSAP après que le DOM soit prêt. Utilisez la fonction de nettoyage retournée par onMount (ou suivez le contexte et nettoyez dans un bloc réactif / destruction du composant) pour annuler. Svelte 5 utilise un cycle de vie différent ; le même principe s'applique : créez lors du "montage" et annulez lors de la "destruction".
<script>
import { onMount } from "svelte";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
let container;
onMount(() => {
if (!container) return;
const ctx = gsap.context(() => {
gsap.to(".box", { x: 100 });
gsap.from(".item", { autoAlpha: 0, stagger: 0.1 });
}, container);
return () => ctx.revert();
});
</script>
<div bind:this={container}>
<div class="box">Boîte</div>
<div class="item">Objet</div>
</div>
- ✅ bind:this={container} — obtenez une référence à l'élément racine pour le passer à gsap.context(scope).
- ✅ return () => ctx.revert() — onMount de Svelte peut retourner une fonction de nettoyage ; appelez ctx.revert() dedans pour que le nettoyage s'exécute lors de la destruction du composant.
Portée des sélecteurs
N'utilisez pas de sélecteurs globaux qui pourraient correspondre à des éléments en dehors du composant actuel. Passez toujours la portée (élément conteneur ou ref) comme deuxième argument à gsap.context(callback, scope) afin que tout sélecteur exécuté à l'intérieur du callback soit limité à ce sous-arbre.
- ✅ gsap.context(() => { gsap.to(".box", ...) }, containerRef) —
.boxest uniquement recherché à l'intérieur decontainerRef. - ❌ Exécuter gsap.to(".box", ...) sans portée de contexte dans un composant peut affecter d'autres instances ou le reste de la page.
Nettoyage des ScrollTrigger
Les instances ScrollTrigger sont créées lorsque vous utilisez la configuration scrollTrigger sur un tween/timeline ou ScrollTrigger.create(). Elles sont incluses dans gsap.context() et annulées lorsque vous appelez ctx.revert(). Donc :
- Créez les ScrollTriggers à l'intérieur du même callback gsap.context() que celui utilisé pour les tweens.
- Appelez ScrollTrigger.refresh() après les changements de mise en page (ex. après le chargement de données) qui affectent les positions des déclencheurs ; dans Vue/Svelte, cela signifie souvent après les mises à jour du DOM (ex. nextTick dans Vue, tick dans Svelte, ou après le chargement de contenu asynchrone).
Quand créer vs détruire
| Cycle de vie | Action |
|---|---|
| Monté | Créez les tweens et ScrollTriggers à l'intérieur de gsap.context(scope). |
| Démontage | Appelez ctx.revert() pour que toutes les animations et ScrollTriggers de ce contexte soient détruits et les styles en ligne annulés. |
Ne créez pas d'animations GSAP dans le setup du composant ou dans un script synchrone de niveau supérieur qui s'exécute avant que l'élément racine n'existe. Attendez onMounted / onMount (ou équivalent) pour que la référence du conteneur soit dans le DOM.
À ne pas faire
- ❌ Créer des tweens ou ScrollTriggers avant que le composant ne soit monté (ex. dans setup sans onMounted) ; les nœuds DOM peuvent ne pas encore exister.
- ❌ Utiliser des chaînes de sélecteurs sans portée (passez le conteneur à gsap.context() comme deuxième argument) pour que les sélecteurs ne ciblent pas des éléments en dehors du composant.
- ❌ Négliger le nettoyage ; appelez toujours ctx.revert() dans onUnmounted / le retour de onMount pour détruire les animations et ScrollTriggers lors de la destruction du composant.
- ❌ Enregistrer des plugins à l'intérieur du corps d'un composant qui s'exécute à chaque rendu (cela ne cause pas de dégâts, c'est juste superflu) ; enregistrez une fois au niveau de l'application.
En savoir plus
- Compétence gsap-react pour les motifs spécifiques à React (useGSAP, contextSafe).