@mehshekari/gsap-motion 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,141 @@
1
+ # Three.js
2
+
3
+ GSAP is the **orchestration layer**, not the renderer. Three owns the scene, the
4
+ camera and the draw loop. GSAP owns *when* values change and *how* they ease
5
+ between states. With React Three Fiber, read [r3f.md](r3f.md) instead.
6
+
7
+ Three does not come with GSAP, and most projects have neither. Check
8
+ `package.json` before writing code against it, and say so if it is not there.
9
+
10
+ ## The division
11
+
12
+ | GSAP | Three |
13
+ |---|---|
14
+ | timing, easing, sequencing | geometry, materials, lights |
15
+ | camera moves between positions | camera projection, the render loop |
16
+ | object transforms over time | the transforms themselves |
17
+ | material property transitions | shaders |
18
+ | tying it all to scroll | rendering the result |
19
+
20
+ Do not rebuild a physics engine or a particle system in GSAP, and do not
21
+ hand-write easing in the render loop when a tween says it in one line.
22
+
23
+ ## Where it is created
24
+
25
+ Three objects are plain JavaScript objects, so GSAP tweens them directly. Nested
26
+ properties are their own targets:
27
+
28
+ ```ts
29
+ gsap.to(mesh.position, { x: 2, y: 1, duration: 1.2, ease: "power3.inOut" });
30
+ gsap.to(mesh.rotation, { y: Math.PI * 2, duration: 3, ease: "none", repeat: -1 });
31
+ gsap.to(mesh.material, { opacity: 0, duration: 0.6 });
32
+ gsap.to(mesh.scale, { x: 1.4, y: 1.4, z: 1.4, duration: 0.5 });
33
+ ```
34
+
35
+ `gsap.to(mesh, { position: { x: 2 } })` does not work. Target `mesh.position`.
36
+
37
+ For a material to animate opacity, `transparent` must already be true — setting
38
+ it mid-tween causes a shader recompile.
39
+
40
+ Shader uniforms are `{ value }` objects, which tween cleanly and are the usual
41
+ bridge between a timeline and a custom effect:
42
+
43
+ ```ts
44
+ gsap.to(material.uniforms.uProgress, { value: 1, duration: 1.5, ease: "power2.inOut" });
45
+ ```
46
+
47
+ Create all of it inside a `gsap.context`, as in [vanilla.md](vanilla.md): the
48
+ lifecycle is the vanilla one, because Three has no component model of its own.
49
+
50
+ ## Where it is torn down
51
+
52
+ Two separate jobs, and GSAP only does the first:
53
+
54
+ - `ctx.revert()` for tweens, timelines and ScrollTriggers.
55
+ - **Three's own disposal is yours**: geometries, materials, textures, render
56
+ targets, and the renderer itself. GSAP's cleanup does not touch them, and
57
+ nothing else will.
58
+
59
+ Stop the render loop too. A `requestAnimationFrame` loop that outlives the
60
+ canvas keeps the GPU busy for a page nobody is looking at.
61
+
62
+ ## Scope
63
+
64
+ Selector strings do not apply: targets are object references. The scoping
65
+ question becomes ownership — a module-level scene shared by two mounts animates
66
+ twice. Build the scene per mount, or key the timeline to the instance.
67
+
68
+ ## Server rendering and hydration
69
+
70
+ None: WebGL exists only in the browser. Import the module that creates the scene
71
+ dynamically, after mount, so the server never evaluates it and the bundle does
72
+ not carry Three on routes that have no canvas.
73
+
74
+ ## Reaching the element
75
+
76
+ By reference, held from where the object was created. Traversing the scene by
77
+ name (`scene.getObjectByName("Cube.003")`) binds the animation to whatever the
78
+ exporter happened to call it, which the next export changes.
79
+
80
+ ## Every frame
81
+
82
+ The render loop is already per-frame; GSAP does not need to be. Use a tween for
83
+ transitions between states, and the loop for values that follow the clock. When
84
+ a tween must write something the loop reads, tween a proxy object and read it in
85
+ the loop rather than calling into Three from `onUpdate` many times per frame.
86
+
87
+ The one exception is a camera with `lookAt`, which overwrites the rotation you
88
+ tween. Tween a proxy target and call `lookAt` from `onUpdate`:
89
+
90
+ ```ts
91
+ const look = { x: 0, y: 0, z: 0 };
92
+ gsap.to(look, {
93
+ x: 2,
94
+ duration: 2,
95
+ onUpdate: () => camera.lookAt(look.x, look.y, look.z),
96
+ });
97
+ ```
98
+
99
+ For scroll-driven 3D, scrub a timeline rather than computing from `scrollY` in
100
+ the loop, with `ease: "none"` on every child, as with any scrub. See
101
+ [scrolltrigger.md](../reference/scrolltrigger.md).
102
+
103
+ ## Page and route changes
104
+
105
+ Dispose on leaving, and rebuild on arriving. A canvas kept across a navigation
106
+ is a live GPU context for a page that is gone.
107
+
108
+ ## Failures
109
+
110
+ | Symptom | Cause |
111
+ |---|---|
112
+ | Nothing moves | tweened `mesh` instead of `mesh.position` |
113
+ | Opacity does nothing, or the frame stutters once | `transparent` set during the tween |
114
+ | The GPU stays busy after leaving the page | the render loop or the renderer was never stopped |
115
+ | Memory grows on every visit | geometries, materials or textures never disposed |
116
+ | The camera snaps back every frame | `lookAt` overwriting a tweened rotation |
117
+ | The page is slow on phones before anything moves | Three in the initial bundle instead of imported dynamically |
118
+
119
+ ## Cost and accessibility
120
+
121
+ 3D is the most expensive thing on a page, and the constraints are harder than
122
+ for DOM animation:
123
+
124
+ - A WebGL canvas runs the GPU continuously. On a phone that is battery and heat.
125
+ - Give the canvas a real fallback — a static image — for low-power devices and
126
+ for `prefers-reduced-motion`. Stopping the animation while leaving a running
127
+ canvas is not a reduced-motion design.
128
+ - Nothing essential — text, navigation, product information — should exist only
129
+ inside the canvas.
130
+
131
+ ## What the audit covers
132
+
133
+ Every rule except the React-specific three, on the `.js`, `.ts` and `.mjs`
134
+ modules that build the scene. It cannot see disposal, the render loop or GPU
135
+ cost: those are read by hand, and measured with
136
+ [performance.md](../reference/performance.md).
137
+
138
+ ## Versions
139
+
140
+ Verified against `gsap@3.15`. Written for `three@0.186`, whose disposal and
141
+ uniform shapes it shares with earlier versions.
@@ -0,0 +1,115 @@
1
+ # Vanilla JavaScript
2
+
3
+ No framework: you own the mount and the unmount, so the only question is
4
+ whether the element this animates can ever be removed.
5
+
6
+ ## Where it is created
7
+
8
+ Inside `gsap.context(fn, root)`, in a function that runs when the element is in
9
+ the document:
10
+
11
+ ```js
12
+ import gsap from "gsap";
13
+ import { ScrollTrigger } from "gsap/ScrollTrigger";
14
+
15
+ gsap.registerPlugin(ScrollTrigger);
16
+
17
+ export function mountReveal(root) {
18
+ const ctx = gsap.context(() => {
19
+ const mm = gsap.matchMedia();
20
+ mm.add(
21
+ {
22
+ motion: "(prefers-reduced-motion: no-preference)",
23
+ reduced: "(prefers-reduced-motion: reduce)",
24
+ },
25
+ ({ conditions }) => {
26
+ if (conditions.reduced) {
27
+ gsap.set("[data-item]", { autoAlpha: 1, y: 0 });
28
+ return;
29
+ }
30
+ gsap.from("[data-item]", {
31
+ autoAlpha: 0,
32
+ y: 24,
33
+ stagger: { amount: 0.4 },
34
+ scrollTrigger: { trigger: root, start: "top 75%" },
35
+ });
36
+ },
37
+ );
38
+ }, root);
39
+
40
+ return () => ctx.revert();
41
+ }
42
+ ```
43
+
44
+ The returned function is the whole teardown contract. Hand it to whatever owns
45
+ the element's life.
46
+
47
+ ## Where it is torn down
48
+
49
+ `ctx.revert()` undoes every tween, ScrollTrigger, Observer, Draggable, SplitText
50
+ and matchMedia created inside the context, and restores inline styles.
51
+
52
+ Timers, `addEventListener` calls and observers are not GSAP's. Clear them in the
53
+ same cleanup.
54
+
55
+ A static page that never removes the element can skip teardown. Anything that
56
+ swaps content — a client-side router, HTMX, Turbo, a modal, a web component —
57
+ cannot: every swap would otherwise leave the previous run's triggers behind.
58
+
59
+ ## Scope
60
+
61
+ The context's second argument is the root. Selector strings inside resolve
62
+ within it, so the same widget mounted twice does not animate the other's
63
+ elements.
64
+
65
+ Plugin config is not scoped: `motionPath.path` and `morphSVG.shape` resolve
66
+ against the whole document. Generate an id per instance rather than hardcoding
67
+ one, in every framework and here too.
68
+
69
+ ## Server rendering and hydration
70
+
71
+ There is none unless you add it. If the markup is rendered by a server template,
72
+ the only rule that carries over is that the script runs after the element
73
+ exists — a `defer`red module, or a `DOMContentLoaded` handler.
74
+
75
+ ## Reaching the element
76
+
77
+ Pass the root in, and query inside it. A module that reaches for
78
+ `document.querySelector` at import time binds to whatever happened to be on the
79
+ first page, and silently animates nothing after a swap.
80
+
81
+ ## Every frame
82
+
83
+ Use `gsap.quickTo` or `gsap.quickSetter` created once, outside the handler, and
84
+ call it from the event. Creating a tween per pointer event allocates one tween
85
+ per frame, each fighting the last.
86
+
87
+ ## Page and route changes
88
+
89
+ If a router swaps content, mount on the new page and revert before the swap. The
90
+ library's own events are the hooks: Turbo's `turbo:load` and
91
+ `turbo:before-render`, HTMX's `htmx:afterSwap`, or your router's equivalent.
92
+ Call `ScrollTrigger.refresh()` after the new content settles.
93
+
94
+ ## Failures
95
+
96
+ | Symptom | Cause |
97
+ |---|---|
98
+ | Animation stops working after a page swap | mounted once at import, never again |
99
+ | Triggers pile up over a session | no `ctx.revert()` before the swap |
100
+ | Selector matches another instance's element | no root passed to `gsap.context` |
101
+ | Scroll positions drift | no `ScrollTrigger.refresh()` after content changed |
102
+
103
+ ## What the audit covers
104
+
105
+ Every rule except the React-specific three: `orphan-tween`, `state-per-event`
106
+ and `shared-plugin-id` apply to React files only. Cost, loops, scrub, listeners,
107
+ unmanaged instances, plugin registration, reduced motion and shipped dev tooling
108
+ are checked here.
109
+
110
+ Keep the animation in a `.js`, `.mjs` or `.ts` module — the files the audit
111
+ reads — rather than in an inline `<script>` in an HTML page, which it does not.
112
+
113
+ ## Versions
114
+
115
+ Verified against `gsap@3.15`. No framework version applies.
@@ -0,0 +1,120 @@
1
+ # Vue and Nuxt
2
+
3
+ Composition API with `<script setup>`. Nuxt renders on the server, so everything
4
+ here is written to run only in the browser.
5
+
6
+ ## Where it is created
7
+
8
+ In `onMounted`, inside a `gsap.context` rooted at the component's element:
9
+
10
+ ```vue
11
+ <script setup>
12
+ import gsap from "gsap";
13
+ import { onMounted, onUnmounted, ref } from "vue";
14
+
15
+ const root = ref(null);
16
+ let ctx;
17
+
18
+ onMounted(() => {
19
+ ctx = gsap.context(() => {
20
+ gsap.from("[data-item]", { autoAlpha: 0, y: 24, stagger: 0.06 });
21
+ }, root.value);
22
+ });
23
+
24
+ onUnmounted(() => ctx?.revert());
25
+ </script>
26
+
27
+ <template>
28
+ <section ref="root">…</section>
29
+ </template>
30
+ ```
31
+
32
+ Animation that reacts to state belongs in a `watch` that adds its tweens to the
33
+ same context with `ctx.add(() => { /* … */ })`, so they are reverted with it
34
+ rather than outliving it.
35
+
36
+ ## Where it is torn down
37
+
38
+ `onUnmounted`, with `ctx.revert()`. That covers every tween, ScrollTrigger,
39
+ Observer, Draggable, SplitText and matchMedia created inside the context.
40
+
41
+ Timers, listeners and observers are yours, in the same hook. A `watch` stopper
42
+ is yours too if you created it outside `setup`'s own scope.
43
+
44
+ `<KeepAlive>` does not unmount: it deactivates. Use `onActivated` and
45
+ `onDeactivated` to pause and resume, or the animation keeps running in a
46
+ component the visitor cannot see.
47
+
48
+ ## Scope
49
+
50
+ `gsap.context(fn, root.value)` scopes selector strings to the component's root,
51
+ so a component used twice on a page does not animate the other's elements.
52
+
53
+ Plugin config is not scoped. `motionPath.path` and `morphSVG.shape` resolve
54
+ against the whole document, so generate an id per instance —
55
+ `useId()` in Vue 3.5 and later, or a module counter — rather than hardcoding
56
+ one.
57
+
58
+ ## Server rendering and hydration
59
+
60
+ `onMounted` never runs on the server, so this is safe under Nuxt as written. Two
61
+ rules follow:
62
+
63
+ - Do not read `window`, `document` or `matchMedia` in `setup`'s body — it runs
64
+ on the server. Read them inside `onMounted`.
65
+ - Render the neutral state on the server and correct it after mount, or the
66
+ hydrated markup disagrees with what was sent.
67
+
68
+ In Nuxt, `<ClientOnly>` is a way to skip server rendering for a widget entirely,
69
+ at the cost of rendering nothing until hydration — a poor trade for a section
70
+ that carries content, a reasonable one for a canvas.
71
+
72
+ ## Reaching the element
73
+
74
+ A template ref for the root, and `data-*` attributes inside it for the parts.
75
+ Querying by tag or position (`root.value.children[2]`) breaks the next time the
76
+ template changes.
77
+
78
+ A child component's root element is reachable with a ref only when it is a
79
+ single-root component; otherwise pass a class or a `data-*` attribute down and
80
+ query it inside the context.
81
+
82
+ ## Every frame
83
+
84
+ Refs, not reactive state. A `ref()` written every frame triggers Vue's
85
+ reactivity and re-renders the component; `gsap.quickTo` writes straight to the
86
+ element. Reactive state is for what exists — is the panel open, which tab is
87
+ active.
88
+
89
+ ## Page and route changes
90
+
91
+ Vue Router replaces the view, and `onUnmounted` runs, so the context goes with
92
+ it. For an exit animation, either put the overlay in the layout, which the
93
+ navigation does not unmount, or use the router's own guards to hold the
94
+ navigation while a timeline plays.
95
+
96
+ Call `ScrollTrigger.refresh()` after the new view settles — in Nuxt, after
97
+ `nextTick()` on the route change.
98
+
99
+ ## Failures
100
+
101
+ | Symptom | Cause |
102
+ |---|---|
103
+ | `window is not defined` during a Nuxt build | `window` read in `setup` instead of `onMounted` |
104
+ | Hydration mismatch warning | state derived from storage or a media query during render |
105
+ | The animation keeps running after leaving the page | `<KeepAlive>` deactivation treated as unmount |
106
+ | Tweens from a `watch` are never reverted | created outside the context instead of through `ctx.add` |
107
+ | Two instances animate each other | no root passed to `gsap.context` |
108
+
109
+ ## What the audit covers
110
+
111
+ From 3.2 the audit reads the `<script>` block of `.vue` files, with offsets
112
+ kept, so reported lines match the file. It checks that a context created in
113
+ `onMounted` is reverted, along with every rule that is not React-specific.
114
+
115
+ `orphan-tween`, `state-per-event` and `shared-plugin-id` are React-only and do
116
+ not run here.
117
+
118
+ ## Versions
119
+
120
+ Verified against `gsap@3.15`. Written for `vue@3.5` and `nuxt@4`.
@@ -177,4 +177,4 @@ project's messages itself could bundle every locale.
177
177
  ## Related
178
178
 
179
179
  [preset/loader.md](../preset/loader.md) · [reference/svg.md](../reference/svg.md)
180
- · [reference/react-nextjs.md](../reference/react-nextjs.md)
180
+ · [adapter/react.md](../adapter/react.md)
@@ -180,7 +180,7 @@ that context** — `MatchMedia`'s constructor registers with whatever context is
180
180
  active (verified against gsap 3.15), and `Context.add` makes itself active while your
181
181
  function runs. An explicit `mm.revert()` there is redundant, not wrong. Created
182
182
  outside one — module scope, a plain `useEffect` — it is yours to revert. See
183
- [react-nextjs.md](react-nextjs.md).
183
+ [react.md](../adapter/react.md).
184
184
 
185
185
  ## `context`
186
186