diff --git a/examples/README.md b/examples/README.md index 03f4c28..4d565dc 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,6 +1,6 @@ # GSAP skills — reference examples -Minimal demos that follow the skills exactly: transforms, autoAlpha, timelines, ScrollTrigger, and (in React) useGSAP with scope and cleanup. +Minimal demos that follow the skills exactly: transforms, autoAlpha, timelines, ScrollTrigger, and framework-specific patterns (useGSAP in React, onMount/onUnmount with gsap.context in Vue/Nuxt). ## Vanilla (HTML + JS) @@ -15,3 +15,15 @@ Minimal demos that follow the skills exactly: transforms, autoAlpha, timelines, - Patterns: `useGSAP()` with `scope: containerRef`, refs for targets, no selectors without scope; cleanup is automatic on unmount. These examples are intended as reference implementations for AI agents and for quick manual verification of the skill patterns. + +## Vue + +- **examples/vue/** — Vite + Vue 3 + ` + + + + \ No newline at end of file diff --git a/examples/nuxt/nuxt.config.ts b/examples/nuxt/nuxt.config.ts new file mode 100644 index 0000000..f773605 --- /dev/null +++ b/examples/nuxt/nuxt.config.ts @@ -0,0 +1,4 @@ +export default defineNuxtConfig({ + ssr: true, + compatibilityDate: '2025-07-15', +}); \ No newline at end of file diff --git a/examples/nuxt/package.json b/examples/nuxt/package.json new file mode 100644 index 0000000..bfbb05c --- /dev/null +++ b/examples/nuxt/package.json @@ -0,0 +1,18 @@ +{ + "name": "gsap-nuxt-example", + "type": "module", + "private": true, + "scripts": { + "build": "nuxt build", + "dev": "nuxt dev", + "generate": "nuxt generate", + "preview": "nuxt preview", + "postinstall": "nuxt prepare" + }, + "dependencies": { + "gsap": "^3.14.2", + "nuxt": "^4.4.2", + "vue": "^3.5.30", + "vue-router": "^5.0.4" + } +} \ No newline at end of file diff --git a/examples/vue/app.vue b/examples/vue/app.vue new file mode 100644 index 0000000..209550b --- /dev/null +++ b/examples/vue/app.vue @@ -0,0 +1,47 @@ +/** + * Minimal GSAP Vue 3 example — follows gsap-frameworks skill: gsap.context, scope, cleanup. + * Scoped selectors via context; automatic revert on unmount. + */ + + + \ No newline at end of file diff --git a/examples/vue/index.html b/examples/vue/index.html new file mode 100644 index 0000000..576c19d --- /dev/null +++ b/examples/vue/index.html @@ -0,0 +1,15 @@ + + + + + + + GSAP Vue 3 — gsap.context + scope + cleanup + + + +
+ + + + \ No newline at end of file diff --git a/examples/vue/main.js b/examples/vue/main.js new file mode 100644 index 0000000..e76a2f6 --- /dev/null +++ b/examples/vue/main.js @@ -0,0 +1,9 @@ +import { createApp } from "vue"; +import { gsap } from "gsap"; +import { ScrollTrigger } from "gsap/ScrollTrigger"; +import App from "./App.vue"; + +// Register all the GSAP Plugins you want only once at this top level file +gsap.registerPlugin(ScrollTrigger); + +createApp(App).mount("#app"); \ No newline at end of file diff --git a/examples/vue/package.json b/examples/vue/package.json new file mode 100644 index 0000000..c9ffbd4 --- /dev/null +++ b/examples/vue/package.json @@ -0,0 +1,19 @@ +{ + "name": "gsap-vue-example", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "gsap": "^3.14.2", + "vue": "^3.5.32" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.5", + "vite": "^8.0.4" + } +} \ No newline at end of file diff --git a/examples/vue/vite.config.js b/examples/vue/vite.config.js new file mode 100644 index 0000000..37d3d6f --- /dev/null +++ b/examples/vue/vite.config.js @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue()], +}); diff --git a/skills/gsap-frameworks/SKILL.md b/skills/gsap-frameworks/SKILL.md index 1ffb90b..3f8af87 100644 --- a/skills/gsap-frameworks/SKILL.md +++ b/skills/gsap-frameworks/SKILL.md @@ -20,6 +20,8 @@ Apply when writing or reviewing GSAP code in Vue (or Nuxt), Svelte (or SvelteKit ## Vue 3 (Composition API) +See `examples/vue/` for a runnable Vite + Vue 3 project demonstrating these patterns. + Use **onMounted** to run GSAP after the component is in the DOM. Use **onUnmounted** to clean up. ```javascript @@ -46,7 +48,7 @@ export default { }); return { container }; - } + }, }; ``` @@ -87,6 +89,120 @@ onUnmounted(() => { ``` +## Nuxt 3 + +> See `examples/nuxt/` for a runnable Nuxt 4 project with plugin registration, lazy loading, and SSR-safe patterns. + +Use a **client-only composable** (`.client.ts` suffix) to register GSAP Plugins, it never runs on the server: + +```typescript +// composables/useGSAP.client.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]; + +// In order to dynamically load all the GSAP plugins +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; + +// Resolves the module type for a given key, then picks the named export matching the key +// this allows to have the type definitions for autocomplete in your code editor +type PluginModule = Awaited>; +type PluginExport = PluginModule[K & keyof PluginModule]; + +export default function () { + // Register all the GSAP Plugins you want at this point + gsap.registerPlugin(ScrollTrigger); + + /* + If you want to lazy load some of the plugins that are + not widely used in your app (for example in just a couple + of components or a single route), you can use this method + */ + async function lazyLoadPlugin(plugin: K): Promise> { + 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 via `useGSAP()`: + +```javascript +import useGSAP from "./composables/useGSAP.client"; + +const { gsap, ScrollTrigger, lazyLoadPlugin } = useGSAP(); +``` + +- ✅ **`.client.ts` suffix** ensures the composable only runs in the browser (SSR-safe). Never import GSAP in server-rendered code. +- ✅ **`useGSAP()`** provides typed access to the gsap instance and lazy load method. +- ✅ **Lazy-load any plugin** (SplitText, MorphSVG, etc.) that is not widely used in your app to reduce initial bundle size. +- ✅ Use **gsap.context(scope)** and **onUnmounted → ctx.revert()** in components, same as Vue 3. + ## Svelte Use **onMount** to run GSAP after the DOM is ready. Use the **returned cleanup function** from onMount (or track the context and clean up in a reactive block / component destroy) to revert. Svelte 5 uses a different lifecycle; the same principle applies: create in “mounted” and revert in “destroyed.” @@ -134,9 +250,9 @@ ScrollTrigger instances are created when you use the `scrollTrigger` config on a ## When to Create vs Kill -| Lifecycle | Action | -|-----------------|--------| -| **Mounted** | Create tweens and ScrollTriggers inside **gsap.context(scope)**. | +| 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.