GSAP with Vue, Svelte, and Other Frameworks
Production guide for integrating GSAP into Vue 3, Nuxt 4, Svelte, and other lifecycle-based component frameworks. Covers scoped selectors via gsap.context(), cleanup on unmount, plugin registration, and ScrollTrigger refresh patterns.
For React specifically, use the gsap-react skill (useGSAP hook, gsap.context()). This skill targets non-React frameworks.
When to Use
Apply this skill when:
- Writing or reviewing GSAP code in Vue 3 (Composition API or
<script setup>) or Nuxt 4.
- Writing or reviewing GSAP code in Svelte or SvelteKit.
- The user asks about GSAP with Vue, Svelte, Nuxt,
onMounted, onMount, onDestroy, or component lifecycle animation.
- Scoping GSAP selectors to a component root to avoid cross-component leakage.
- Setting up GSAP plugin registration (ScrollTrigger, SplitText, etc.) in a framework context.
- Cleaning up tweens and ScrollTriggers on component unmount/destroy.
Related skills:
- gsap-core — tweens, basic properties, easing.
- gsap-timeline — timeline construction and sequencing.
- gsap-scrolltrigger — scroll-driven animation, pinning, scrubbing.
- gsap-react — React-specific patterns (useGSAP, contextSafe).
Prerequisites
- GSAP installed in the project:
npm install gsap (or pnpm add gsap, yarn add gsap).
- For Vue: Vue 3.x with Composition API available.
- For Nuxt: Nuxt 4.x project with
composables/ directory support.
- For Svelte: Svelte 4.x or 5.x (lifecycle APIs differ slightly; see Svelte section).
- For ScrollTrigger or other plugins: import from
gsap/<PluginName> and register once at app level.
Windows host note (PowerShell): All CLI commands below assume PowerShell as the default shell. On macOS/Linux, commands are equivalent unless noted.
Procedure
Core Principles (All Frameworks)
- Create tweens and ScrollTriggers after the component's DOM is available (e.g.,
onMounted, onMount).
- Kill or revert them in the unmount (or equivalent) cleanup so nothing runs on detached nodes and there are no leaks.
- Scope selectors to the component root so
.box and similar only match elements inside that component, not the rest of the page.
Vue 3 (Composition API)
- Import
onMounted, onUnmounted, and ref from Vue.
- Import
gsap and any plugins (e.g., ScrollTrigger).
- Register plugins once at app level (e.g., in
main.js), not inside every component.
- In
onMounted, create a gsap.context() with the container ref as scope.
- In
onUnmounted, call ctx.revert().
import { onMounted, onUnmounted, ref } from "vue";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger); // once per app, e.g. in 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 };
},
};
Key points:
gsap.context(callback, scope) — pass container.value as the second argument so selectors like .item are scoped to that root.
- All animations and ScrollTriggers created inside the callback are tracked and reverted when
ctx.revert() is called.
- Always call
ctx.revert() in onUnmounted so tweens and ScrollTriggers are killed and inline styles reverted.
Vue 3 (<script setup>)
Same pattern with <script setup> syntax:
<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">Box</div>
<div class="item">Item</div>
</div>
</template>
Nuxt 4
- Create a reusable composable at
composables/useGSAP.ts to register GSAP plugins and provide lazy-loading for infrequently used plugins.
- Access
gsap, ScrollTrigger, and lazyLoadPlugin in components via useGSAP().
- Use
gsap.context(scope) and onUnmounted ctx.revert() in components, same as Vue 3.
// 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;
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;
type PluginModule<K extends Plugins> = Awaited<ReturnType<PluginMap[K]>>;
type PluginExport<K extends Plugins> = PluginModule<K>[K & keyof PluginModule<K>];
export default function () {
gsap.registerPlugin(ScrollTrigger);
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 };
}
Access in components:
const { gsap, ScrollTrigger, lazyLoadPlugin } = useGSAP();
useGSAP() provides typed access to the gsap instance and lazy-load method.
- Lazy-load any plugin (SplitText, MorphSVG, etc.) that is not widely used to reduce initial bundle size.
- Use
gsap.context(scope) and onUnmounted ctx.revert() in components, same as Vue 3.
Svelte
- Import
onMount from Svelte.
- Import
gsap and any plugins.
- Use
bind:this={container} to get a reference to the root element.
- In
onMount, create a gsap.context() with the container as scope.
- Return a cleanup function from
onMount that calls ctx.revert().
<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">Box</div>
<div class="item">Item</div>
</div>
bind:this={container} — get a reference to the root element to pass to gsap.context(scope).
return () => ctx.revert() — Svelte's onMount can return a cleanup function; ctx.revert() runs when the component is destroyed.
- Svelte 5 note: Svelte 5 uses a different lifecycle API (runes); the same principle applies: create in mounted and revert in destroyed.
Scoping Selectors
Always pass the scope (container element or ref) as the second argument to gsap.context(callback, scope):
gsap.context(() => { gsap.to(".box", ...) }, containerRef) — .box is only searched inside containerRef.
- Running
gsap.to(".box", ...) without a context scope in a component can affect other instances or the rest of the page.
ScrollTrigger Cleanup and Refresh
- ScrollTrigger instances are created when you use the
scrollTrigger config on a tween/timeline or ScrollTrigger.create().
- They are included in
gsap.context() and reverted when you call ctx.revert().
- Create ScrollTriggers inside the same
gsap.context() callback you use for tweens.
- Call
ScrollTrigger.refresh() after layout changes (e.g., after data loads) that affect trigger positions.
- In Vue: use
nextTick after DOM updates.
- In Svelte: use
tick after DOM updates.
- After async content load: call
ScrollTrigger.refresh() once content is rendered.
When to Create vs Kill
| Lifecycle |
Action |
| Mounted |
Create tweens and ScrollTriggers inside gsap.context(scope). |
| Unmount / Destroy |
Call ctx.revert() so all animations and ScrollTriggers in that context are killed and inline styles reverted. |
Do not create GSAP animations in the component's setup or in a synchronous top-level script that runs before the root element exists. Wait for onMounted / onMount (or equivalent) so the container ref is in the DOM.
Pitfalls
- Creating tweens before mount: Do not create tweens or ScrollTriggers before the component is mounted (e.g., in
setup without onMounted); the DOM nodes may not exist yet.
- Ungscoped selectors: Do not use selector strings without a scope. Always pass the container to
gsap.context() as the second argument so selectors don't match elements outside the component.
- Skipping cleanup: Always call
ctx.revert() in onUnmounted / onMount's return so animations and ScrollTriggers are killed when the component is destroyed. Leaked tweens on detached nodes cause performance degradation and visual bugs.
- Registering plugins per render: Do not register plugins inside a component body that runs every render. It doesn't break anything but is wasteful. Register once at app level (e.g.,
main.js or a Nuxt plugin/composable).
- Forgetting ScrollTrigger.refresh(): After layout changes, image loads, font loads, or async data rendering, trigger positions may be stale. Call
ScrollTrigger.refresh() after the DOM updates.
- Animating pinned trigger elements in ways that invalidate measurements: Avoid animating layout properties (width, height, top, left) on pinned elements; this can cause ScrollTrigger measurement errors.
- Not respecting prefers-reduced-motion: Provide simpler transitions, instant states, or user-controlled playback for users with reduced-motion preference.
Verification
- Check that animations are scoped: Open browser DevTools Console and verify no GSAP inline styles appear on elements outside the component root after mount.
- Check cleanup on unmount: Navigate away from the component (or conditionally destroy it), then run in Console:
ScrollTrigger.getAll().length
The count should not include stale triggers from the unmounted component.
- Check for leaked tweens: After unmount, verify no console errors about tweening detached or null nodes.
- Check ScrollTrigger positions: After layout changes or data loads, run:
ScrollTrigger.refresh()
and verify trigger start/end positions update correctly.
- Check reduced-motion: In DevTools, emulate
prefers-reduced-motion: reduce (Rendering tab in Chrome DevTools) and verify animations degrade gracefully.
- Type check (Nuxt/TS): Run
npx vue-tsc --noEmit in PowerShell to verify the useGSAP composable types resolve correctly.
- Build check: Run
npm run build (or pnpm build) and confirm no GSAP-related import or tree-shaking errors.
Examples
Runnable Projects
- See
examples/vue/ for a runnable Vite + Vue 3 project demonstrating these patterns.
- See
examples/nuxt/ for a runnable Nuxt 4 project with plugin registration, lazy loading, and SSR-safe patterns.
When to load reference files: If the user needs a full project scaffold, read the files under examples/vue/ or examples/nuxt/ to provide copy-pasteable project structure. For plugin-specific API details, refer the user to the gsap-scrolltrigger or gsap-core skills.
Related Skills
- gsap-core — tweens, properties, easing fundamentals.
- gsap-timeline — timeline construction, sequencing, labels.
- gsap-scrolltrigger — scroll-driven animation, pinning, scrubbing, refresh patterns.
- gsap-react — React-specific patterns (useGSAP hook, contextSafe, cleanup).
References
1---2name: gsap-frameworks3description: Integrates GSAP into Vue 3, Nuxt 4, Svelte, and SvelteKit via onMounted/onMount, gsap.context scoped selectors, and ctx.revert() cleanup. Use when writing or reviewing GSAP in those non-React frameworks, including ScrollTrigger plugin registration. Not for React useGSAP/@gsap/react (gsap-web-animations) or GSAP tween/timeline recipes without a component lifecycle.4---5
6# GSAP with Vue, Svelte, and Other Frameworks
7
8Production guide for integrating GSAP into Vue 3, Nuxt 4, Svelte, and other lifecycle-based component frameworks. Covers scoped selectors via `gsap.context()`, cleanup on unmount, plugin registration, and ScrollTrigger refresh patterns.
9
10> **For React specifically**, use the **gsap-react** skill (useGSAP hook, gsap.context()). This skill targets non-React frameworks.
11
12## When to Use
13
14Apply this skill when:
15
16- Writing or reviewing GSAP code in **Vue 3** (Composition API or `<script setup>`) or **Nuxt 4**.
17- Writing or reviewing GSAP code in **Svelte** or **SvelteKit**.
18- The user asks about GSAP with Vue, Svelte, Nuxt, `onMounted`, `onMount`, `onDestroy`, or component lifecycle animation.
19- Scoping GSAP selectors to a component root to avoid cross-component leakage.
20- Setting up GSAP plugin registration (ScrollTrigger, SplitText, etc.) in a framework context.
21- Cleaning up tweens and ScrollTriggers on component unmount/destroy.
22
23**Related skills:**
24
25- **gsap-core** — tweens, basic properties, easing.
26- **gsap-timeline** — timeline construction and sequencing.
27- **gsap-scrolltrigger** — scroll-driven animation, pinning, scrubbing.
28- **gsap-react** — React-specific patterns (useGSAP, contextSafe).
29
30## Prerequisites
31
32- GSAP installed in the project: `npm install gsap` (or `pnpm add gsap`, `yarn add gsap`).
33- For Vue: Vue 3.x with Composition API available.
34- For Nuxt: Nuxt 4.x project with `composables/` directory support.
35- For Svelte: Svelte 4.x or 5.x (lifecycle APIs differ slightly; see Svelte section).
36- For ScrollTrigger or other plugins: import from `gsap/<PluginName>` and register once at app level.
37
38**Windows host note (PowerShell):** All CLI commands below assume PowerShell as the default shell. On macOS/Linux, commands are equivalent unless noted.
39
40## Procedure
41
42### Core Principles (All Frameworks)
43
441. **Create** tweens and ScrollTriggers **after** the component's DOM is available (e.g., `onMounted`, `onMount`).
452. **Kill or revert** them in the **unmount** (or equivalent) cleanup so nothing runs on detached nodes and there are no leaks.
463. **Scope selectors** to the component root so `.box` and similar only match elements inside that component, not the rest of the page.
47
48### Vue 3 (Composition API)
49
501. Import `onMounted`, `onUnmounted`, and `ref` from Vue.
512. Import `gsap` and any plugins (e.g., `ScrollTrigger`).
523. Register plugins once at app level (e.g., in `main.js`), not inside every component.
534. In `onMounted`, create a `gsap.context()` with the container ref as scope.
545. In `onUnmounted`, call `ctx.revert()`.
55
56```javascript
57import { onMounted, onUnmounted, ref } from "vue";
58import { gsap } from "gsap";
59import { ScrollTrigger } from "gsap/ScrollTrigger";
60gsap.registerPlugin(ScrollTrigger); // once per app, e.g. in main.js
61
62export default {
63 setup() {
64 const container = ref(null);
65 let ctx;
66
67 onMounted(() => {
68 if (!container.value) return;
69 ctx = gsap.context(() => {
70 gsap.to(".box", { x: 100, duration: 0.6 });
71 gsap.from(".item", { autoAlpha: 0, y: 20, stagger: 0.1 });
72 }, container.value);
73 });
74
75 onUnmounted(() => {
76 ctx?.revert();
77 });
78
79 return { container };
80 },
81};
82```
83
84**Key points:**
85
86- `gsap.context(callback, scope)` — pass `container.value` as the second argument so selectors like `.item` are scoped to that root.
87- All animations and ScrollTriggers created inside the callback are tracked and reverted when `ctx.revert()` is called.
88- Always call `ctx.revert()` in `onUnmounted` so tweens and ScrollTriggers are killed and inline styles reverted.
89
90### Vue 3 (`<script setup>`)
91
92Same pattern with `<script setup>` syntax:
93
94```vue
95<script setup>
96import { onMounted, onUnmounted, ref } from "vue";
97import { gsap } from "gsap";
98import { ScrollTrigger } from "gsap/ScrollTrigger";
99
100const container = ref(null);
101let ctx;
102
103onMounted(() => {
104 if (!container.value) return;
105 ctx = gsap.context(() => {
106 gsap.to(".box", { x: 100 });
107 gsap.from(".item", { autoAlpha: 0, stagger: 0.1 });
108 }, container.value);
109});
110
111onUnmounted(() => {
112 ctx?.revert();
113});
114</script>
115
116<template>
117 <div ref="container">
118 <div class="box">Box</div>
119 <div class="item">Item</div>
120 </div>
121</template>
122```
123
124### Nuxt 4
125
1261. Create a reusable composable at `composables/useGSAP.ts` to register GSAP plugins and provide lazy-loading for infrequently used plugins.
1272. Access `gsap`, `ScrollTrigger`, and `lazyLoadPlugin` in components via `useGSAP()`.
1283. Use `gsap.context(scope)` and `onUnmounted` `ctx.revert()` in components, same as Vue 3.
129
130```typescript
131// composables/useGSAP.ts
132import { gsap } from "gsap";
133import { ScrollTrigger } from "gsap/ScrollTrigger";
134
135const PLUGINS = [
136 "CSSRulePlugin", "CustomBounce", "CustomEase", "CustomWiggle",
137 "Draggable", "DrawSVGPlugin", "EaselPlugin", "EasePack", "Flip",
138 "GSDevTools", "InertiaPlugin", "MorphSVGPlugin", "MotionPathHelper",
139 "MotionPathPlugin", "Observer", "Physics2DPlugin", "PhysicsPropsPlugin",
140 "PixiPlugin", "ScrambleTextPlugin", "ScrollSmoother", "ScrollToPlugin",
141 "ScrollTrigger", "SplitText", "TextPlugin",
142] as const;
143
144const pluginMap = {
145 CustomEase: () => import("gsap/CustomEase"),
146 Draggable: () => import("gsap/Draggable"),
147 CSSRulePlugin: () => import("gsap/CSSRulePlugin"),
148 EaselPlugin: () => import("gsap/EaselPlugin"),
149 EasePack: () => import("gsap/EasePack"),
150 Flip: () => import("gsap/Flip"),
151 MotionPathPlugin: () => import("gsap/MotionPathPlugin"),
152 Observer: () => import("gsap/Observer"),
153 PixiPlugin: () => import("gsap/PixiPlugin"),
154 ScrollToPlugin: () => import("gsap/ScrollToPlugin"),
155 ScrollTrigger: () => import("gsap/ScrollTrigger"),
156 TextPlugin: () => import("gsap/TextPlugin"),
157 DrawSVGPlugin: () => import("gsap/DrawSVGPlugin"),
158 Physics2DPlugin: () => import("gsap/Physics2DPlugin"),
159 PhysicsPropsPlugin: () => import("gsap/PhysicsPropsPlugin"),
160 ScrambleTextPlugin: () => import("gsap/ScrambleTextPlugin"),
161 CustomBounce: () => import("gsap/CustomBounce"),
162 CustomWiggle: () => import("gsap/CustomWiggle"),
163 GSDevTools: () => import("gsap/GSDevTools"),
164 InertiaPlugin: () => import("gsap/InertiaPlugin"),
165 MorphSVGPlugin: () => import("gsap/MorphSVGPlugin"),
166 MotionPathHelper: () => import("gsap/MotionPathHelper"),
167 ScrollSmoother: () => import("gsap/ScrollSmoother"),
168 SplitText: () => import("gsap/SplitText"),
169} as const;
170
171type PluginMap = typeof pluginMap;
172type Plugins = keyof PluginMap;
173type PluginModule<K extends Plugins> = Awaited<ReturnType<PluginMap[K]>>;
174type PluginExport<K extends Plugins> = PluginModule<K>[K & keyof PluginModule<K>];
175
176export default function () {
177 gsap.registerPlugin(ScrollTrigger);
178
179 async function lazyLoadPlugin<K extends Plugins>(plugin: K): Promise<PluginExport<K>> {
180 const loader = pluginMap[plugin];
181 const m = await loader();
182 const p = (m as any)[plugin];
183 gsap.registerPlugin(p);
184 return p;
185 }
186
187 return { gsap, ScrollTrigger, lazyLoadPlugin };
188}
189```
190
191Access in components:
192
193```javascript
194const { gsap, ScrollTrigger, lazyLoadPlugin } = useGSAP();
195```
196
197- `useGSAP()` provides typed access to the gsap instance and lazy-load method.
198- Lazy-load any plugin (SplitText, MorphSVG, etc.) that is not widely used to reduce initial bundle size.
199- Use `gsap.context(scope)` and `onUnmounted` `ctx.revert()` in components, same as Vue 3.
200
201### Svelte
202
2031. Import `onMount` from Svelte.
2042. Import `gsap` and any plugins.
2053. Use `bind:this={container}` to get a reference to the root element.
2064. In `onMount`, create a `gsap.context()` with the container as scope.
2075. Return a cleanup function from `onMount` that calls `ctx.revert()`.
208
209```svelte
210<script>
211 import { onMount } from "svelte";
212 import { gsap } from "gsap";
213 import { ScrollTrigger } from "gsap/ScrollTrigger";
214
215 let container;
216
217 onMount(() => {
218 if (!container) return;
219 const ctx = gsap.context(() => {
220 gsap.to(".box", { x: 100 });
221 gsap.from(".item", { autoAlpha: 0, stagger: 0.1 });
222 }, container);
223 return () => ctx.revert();
224 });
225</script>
226
227<div bind:this={container}>
228 <div class="box">Box</div>
229 <div class="item">Item</div>
230</div>
231```
232
233- `bind:this={container}` — get a reference to the root element to pass to `gsap.context(scope)`.
234- `return () => ctx.revert()` — Svelte's `onMount` can return a cleanup function; `ctx.revert()` runs when the component is destroyed.
235- **Svelte 5 note:** Svelte 5 uses a different lifecycle API (runes); the same principle applies: create in mounted and revert in destroyed.
236
237### Scoping Selectors
238
239Always pass the **scope** (container element or ref) as the second argument to `gsap.context(callback, scope)`:
240
241- `gsap.context(() => { gsap.to(".box", ...) }, containerRef)` — `.box` is only searched inside `containerRef`.
242- Running `gsap.to(".box", ...)` without a context scope in a component can affect other instances or the rest of the page.
243
244### ScrollTrigger Cleanup and Refresh
245
246- ScrollTrigger instances are created when you use the `scrollTrigger` config on a tween/timeline or `ScrollTrigger.create()`.
247- They are **included** in `gsap.context()` and reverted when you call `ctx.revert()`.
248- Create ScrollTriggers inside the same `gsap.context()` callback you use for tweens.
249- Call `ScrollTrigger.refresh()` after layout changes (e.g., after data loads) that affect trigger positions.
250 - In Vue: use `nextTick` after DOM updates.
251 - In Svelte: use `tick` after DOM updates.
252 - After async content load: call `ScrollTrigger.refresh()` once content is rendered.
253
254### When to Create vs Kill
255
256| Lifecycle | Action |
257|---|---|
258| **Mounted** | Create tweens and ScrollTriggers inside `gsap.context(scope)`. |
259| **Unmount / Destroy** | Call `ctx.revert()` so all animations and ScrollTriggers in that context are killed and inline styles reverted. |
260
261Do not create GSAP animations in the component's setup or in a synchronous top-level script that runs before the root element exists. Wait for `onMounted` / `onMount` (or equivalent) so the container ref is in the DOM.
262
263## Pitfalls
264
265- **Creating tweens before mount:** Do not create tweens or ScrollTriggers before the component is mounted (e.g., in `setup` without `onMounted`); the DOM nodes may not exist yet.
266- **Ungscoped selectors:** Do not use selector strings without a scope. Always pass the container to `gsap.context()` as the second argument so selectors don't match elements outside the component.
267- **Skipping cleanup:** Always call `ctx.revert()` in `onUnmounted` / `onMount`'s return so animations and ScrollTriggers are killed when the component is destroyed. Leaked tweens on detached nodes cause performance degradation and visual bugs.
268- **Registering plugins per render:** Do not register plugins inside a component body that runs every render. It doesn't break anything but is wasteful. Register once at app level (e.g., `main.js` or a Nuxt plugin/composable).
269- **Forgetting ScrollTrigger.refresh():** After layout changes, image loads, font loads, or async data rendering, trigger positions may be stale. Call `ScrollTrigger.refresh()` after the DOM updates.
270- **Animating pinned trigger elements in ways that invalidate measurements:** Avoid animating layout properties (width, height, top, left) on pinned elements; this can cause ScrollTrigger measurement errors.
271- **Not respecting prefers-reduced-motion:** Provide simpler transitions, instant states, or user-controlled playback for users with reduced-motion preference.
272
273## Verification
274
2751. **Check that animations are scoped:** Open browser DevTools Console and verify no GSAP inline styles appear on elements outside the component root after mount.
2762. **Check cleanup on unmount:** Navigate away from the component (or conditionally destroy it), then run in Console:
277 ```javascript
278 ScrollTrigger.getAll().length
279 ```
280 The count should not include stale triggers from the unmounted component.
2813. **Check for leaked tweens:** After unmount, verify no console errors about tweening detached or null nodes.
2824. **Check ScrollTrigger positions:** After layout changes or data loads, run:
283 ```javascript
284 ScrollTrigger.refresh()
285 ```
286 and verify trigger start/end positions update correctly.
2875. **Check reduced-motion:** In DevTools, emulate `prefers-reduced-motion: reduce` (Rendering tab in Chrome DevTools) and verify animations degrade gracefully.
2886. **Type check (Nuxt/TS):** Run `npx vue-tsc --noEmit` in PowerShell to verify the `useGSAP` composable types resolve correctly.
2897. **Build check:** Run `npm run build` (or `pnpm build`) and confirm no GSAP-related import or tree-shaking errors.
290
291## Examples
292
293### Runnable Projects
294
295- See `examples/vue/` for a runnable Vite + Vue 3 project demonstrating these patterns.
296- See `examples/nuxt/` for a runnable Nuxt 4 project with plugin registration, lazy loading, and SSR-safe patterns.
297
298> **When to load reference files:** If the user needs a full project scaffold, read the files under `examples/vue/` or `examples/nuxt/` to provide copy-pasteable project structure. For plugin-specific API details, refer the user to the **gsap-scrolltrigger** or **gsap-core** skills.
299
300## Related Skills
301
302- **gsap-core** — tweens, properties, easing fundamentals.
303- **gsap-timeline** — timeline construction, sequencing, labels.
304- **gsap-scrolltrigger** — scroll-driven animation, pinning, scrubbing, refresh patterns.
305- **gsap-react** — React-specific patterns (useGSAP hook, contextSafe, cleanup).
306
307## References
308
309- GSAP docs: https://gsap.com/docs/v3/
310- GSAP ScrollTrigger docs: https://gsap.com/docs/v3/Plugins/ScrollTrigger/
311- GSAP context() docs: https://gsap.com/docs/v3/GSAP/gsap.context()
312- Vue 3 Composition API: https://vuejs.org/api/composition-api-lifecycle.html
313- Nuxt 4 composables: https://nuxt.com/docs/guide/directory-structure/composables
314- Svelte onMount: https://svelte.dev/docs/svelte/onMount
315- W3C WCAG 2.2: https://www.w3.org/TR/WCAG22/
316- MDN prefers-reduced-motion: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion