@mehshekari/gsap-motion 4.3.0 → 4.4.2
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.
- package/README.md +1 -1
- package/bin/gsap-motion.mjs +7 -0
- package/package.json +1 -1
- package/skill/SKILL.md +8 -8
- package/skill/adapter/next.md +1 -2
- package/skill/adapter/r3f.md +4 -1
- package/skill/adapter/react.md +5 -1
- package/skill/adapter/three.md +2 -0
- package/skill/example/magnetic-button.md +101 -101
- package/skill/preset/cursor.md +184 -184
- package/skill/preset/magnetic.md +155 -155
- package/skill/reference/devices.md +10 -0
- package/skill/reference/flip.md +220 -220
- package/skill/reference/interaction.md +1 -0
- package/skill/reference/performance.md +87 -0
- package/skill/reference/refine.md +54 -0
- package/skill/reference/routing.md +11 -1
- package/skill/reference/scrolltrigger.md +79 -0
- package/skill/scripts/audit-gsap.mjs +4 -0
- package/skill/scripts/capture-motion.mjs +120 -10
- package/skill/scripts/lib/ast.mjs +58 -9
- package/skill/scripts/lib/rules.mjs +134 -5
- package/skill/scripts/review-motion.mjs +167 -8
- package/skill/template/useScrollScene.ts +126 -126
- package/skill/template/useTimeline.ts +110 -110
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useGSAP } from "@gsap/react";
|
|
4
|
-
import gsap from "gsap";
|
|
5
|
-
import { useCallback, useRef } from "react";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Starting point for a component-scoped GSAP timeline.
|
|
9
|
-
*
|
|
10
|
-
* Copy it into the project's hooks folder or next to the component that owns
|
|
11
|
-
* it, rename it, and replace the timeline body. The structure is the part worth
|
|
12
|
-
* keeping: plugin registration at module scope, a scoped `useGSAP`, a
|
|
13
|
-
* `matchMedia` branch that treats reduced motion as a design rather than an off
|
|
14
|
-
* switch, and an explicit teardown for the things the GSAP context does not
|
|
15
|
-
* own.
|
|
16
|
-
*
|
|
17
|
-
* Worth typechecking where the skill is installed: add the skill's `template/`
|
|
18
|
-
* folder to `tsconfig.json`'s `include`. TypeScript's globs skip dot-directories
|
|
19
|
-
* such as `.claude/`, and an example that has quietly stopped compiling is worse
|
|
20
|
-
* than none.
|
|
21
|
-
*/
|
|
22
|
-
gsap.registerPlugin(useGSAP);
|
|
23
|
-
|
|
24
|
-
/** One place for the numbers, so retiming is not a hunt through the body. */
|
|
25
|
-
const TIMING = {
|
|
26
|
-
enter: 0.8,
|
|
27
|
-
stagger: 0.08,
|
|
28
|
-
hold: 0.4,
|
|
29
|
-
} as const;
|
|
30
|
-
|
|
31
|
-
export function useTimeline() {
|
|
32
|
-
const rootRef = useRef<HTMLDivElement>(null);
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Kept on a ref rather than returned directly: the timeline is built inside
|
|
36
|
-
* the effect, and a caller that wants to replay it needs a stable handle that
|
|
37
|
-
* does not exist until then.
|
|
38
|
-
*/
|
|
39
|
-
const timelineRef = useRef<gsap.core.Timeline | null>(null);
|
|
40
|
-
|
|
41
|
-
useGSAP(
|
|
42
|
-
() => {
|
|
43
|
-
/**
|
|
44
|
-
* `matchMedia` rather than a one-off query, because a visitor can change
|
|
45
|
-
* the preference or the viewport while the component is mounted. GSAP
|
|
46
|
-
* reverts the losing branch and builds the other one; a plain `if` leaves
|
|
47
|
-
* the first animation running forever.
|
|
48
|
-
*/
|
|
49
|
-
const mm = gsap.matchMedia();
|
|
50
|
-
|
|
51
|
-
mm.add(
|
|
52
|
-
{
|
|
53
|
-
motion: "(prefers-reduced-motion: no-preference)",
|
|
54
|
-
reduced: "(prefers-reduced-motion: reduce)",
|
|
55
|
-
},
|
|
56
|
-
(context) => {
|
|
57
|
-
const { reduced } = context.conditions as Record<string, boolean>;
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* The reduced branch must set the END state, not merely skip the
|
|
61
|
-
* tween. A `from` that never runs leaves its target at the authored
|
|
62
|
-
* start — which hides the content rather than calming it. This is the
|
|
63
|
-
* most common reduced-motion bug there is.
|
|
64
|
-
*/
|
|
65
|
-
if (reduced) {
|
|
66
|
-
gsap.set("[data-item]", { autoAlpha: 1, y: 0 });
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
timelineRef.current = gsap
|
|
71
|
-
.timeline({ defaults: { ease: "power3.out" } })
|
|
72
|
-
.from("[data-item]", {
|
|
73
|
-
y: 32,
|
|
74
|
-
autoAlpha: 0,
|
|
75
|
-
duration: TIMING.enter,
|
|
76
|
-
stagger: TIMING.stagger,
|
|
77
|
-
})
|
|
78
|
-
/**
|
|
79
|
-
* Positions, not delays. `<` starts a child with the previous one
|
|
80
|
-
* and survives a duration change upstream, where `-=0.3` silently
|
|
81
|
-
* drifts.
|
|
82
|
-
*/
|
|
83
|
-
.to("[data-accent]", { scaleX: 1, duration: 0.5 }, "<0.2");
|
|
84
|
-
},
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Nothing is returned, and that is deliberate. `matchMedia` registers
|
|
89
|
-
* itself with whatever GSAP context is active when it is constructed, so
|
|
90
|
-
* this one is reverted along with every tween above — as are Observer,
|
|
91
|
-
* Draggable, SplitText and ScrollTrigger when built in here.
|
|
92
|
-
*
|
|
93
|
-
* What a context never sees is the rest: timers, raw `addEventListener`,
|
|
94
|
-
* ResizeObserver, Three.js disposal. Return a cleanup for those.
|
|
95
|
-
*/
|
|
96
|
-
},
|
|
97
|
-
{ scope: rootRef },
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Playing a timeline that already exists creates nothing, so this needs no
|
|
102
|
-
* `contextSafe` — the context already owns it. Wrapping it would also be a
|
|
103
|
-
* lint error, because `contextSafe` runs during render and reading a ref
|
|
104
|
-
* there is what React forbids. Reserve `contextSafe` for a handler that
|
|
105
|
-
* *creates* a tween.
|
|
106
|
-
*/
|
|
107
|
-
const replay = useCallback(() => timelineRef.current?.restart(), []);
|
|
108
|
-
|
|
109
|
-
return { rootRef, replay };
|
|
110
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useGSAP } from "@gsap/react";
|
|
4
|
+
import gsap from "gsap";
|
|
5
|
+
import { useCallback, useRef } from "react";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Starting point for a component-scoped GSAP timeline.
|
|
9
|
+
*
|
|
10
|
+
* Copy it into the project's hooks folder or next to the component that owns
|
|
11
|
+
* it, rename it, and replace the timeline body. The structure is the part worth
|
|
12
|
+
* keeping: plugin registration at module scope, a scoped `useGSAP`, a
|
|
13
|
+
* `matchMedia` branch that treats reduced motion as a design rather than an off
|
|
14
|
+
* switch, and an explicit teardown for the things the GSAP context does not
|
|
15
|
+
* own.
|
|
16
|
+
*
|
|
17
|
+
* Worth typechecking where the skill is installed: add the skill's `template/`
|
|
18
|
+
* folder to `tsconfig.json`'s `include`. TypeScript's globs skip dot-directories
|
|
19
|
+
* such as `.claude/`, and an example that has quietly stopped compiling is worse
|
|
20
|
+
* than none.
|
|
21
|
+
*/
|
|
22
|
+
gsap.registerPlugin(useGSAP);
|
|
23
|
+
|
|
24
|
+
/** One place for the numbers, so retiming is not a hunt through the body. */
|
|
25
|
+
const TIMING = {
|
|
26
|
+
enter: 0.8,
|
|
27
|
+
stagger: 0.08,
|
|
28
|
+
hold: 0.4,
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
export function useTimeline() {
|
|
32
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Kept on a ref rather than returned directly: the timeline is built inside
|
|
36
|
+
* the effect, and a caller that wants to replay it needs a stable handle that
|
|
37
|
+
* does not exist until then.
|
|
38
|
+
*/
|
|
39
|
+
const timelineRef = useRef<gsap.core.Timeline | null>(null);
|
|
40
|
+
|
|
41
|
+
useGSAP(
|
|
42
|
+
() => {
|
|
43
|
+
/**
|
|
44
|
+
* `matchMedia` rather than a one-off query, because a visitor can change
|
|
45
|
+
* the preference or the viewport while the component is mounted. GSAP
|
|
46
|
+
* reverts the losing branch and builds the other one; a plain `if` leaves
|
|
47
|
+
* the first animation running forever.
|
|
48
|
+
*/
|
|
49
|
+
const mm = gsap.matchMedia();
|
|
50
|
+
|
|
51
|
+
mm.add(
|
|
52
|
+
{
|
|
53
|
+
motion: "(prefers-reduced-motion: no-preference)",
|
|
54
|
+
reduced: "(prefers-reduced-motion: reduce)",
|
|
55
|
+
},
|
|
56
|
+
(context) => {
|
|
57
|
+
const { reduced } = context.conditions as Record<string, boolean>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The reduced branch must set the END state, not merely skip the
|
|
61
|
+
* tween. A `from` that never runs leaves its target at the authored
|
|
62
|
+
* start — which hides the content rather than calming it. This is the
|
|
63
|
+
* most common reduced-motion bug there is.
|
|
64
|
+
*/
|
|
65
|
+
if (reduced) {
|
|
66
|
+
gsap.set("[data-item]", { autoAlpha: 1, y: 0 });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
timelineRef.current = gsap
|
|
71
|
+
.timeline({ defaults: { ease: "power3.out" } })
|
|
72
|
+
.from("[data-item]", {
|
|
73
|
+
y: 32,
|
|
74
|
+
autoAlpha: 0,
|
|
75
|
+
duration: TIMING.enter,
|
|
76
|
+
stagger: TIMING.stagger,
|
|
77
|
+
})
|
|
78
|
+
/**
|
|
79
|
+
* Positions, not delays. `<` starts a child with the previous one
|
|
80
|
+
* and survives a duration change upstream, where `-=0.3` silently
|
|
81
|
+
* drifts.
|
|
82
|
+
*/
|
|
83
|
+
.to("[data-accent]", { scaleX: 1, duration: 0.5 }, "<0.2");
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Nothing is returned, and that is deliberate. `matchMedia` registers
|
|
89
|
+
* itself with whatever GSAP context is active when it is constructed, so
|
|
90
|
+
* this one is reverted along with every tween above — as are Observer,
|
|
91
|
+
* Draggable, SplitText and ScrollTrigger when built in here.
|
|
92
|
+
*
|
|
93
|
+
* What a context never sees is the rest: timers, raw `addEventListener`,
|
|
94
|
+
* ResizeObserver, Three.js disposal. Return a cleanup for those.
|
|
95
|
+
*/
|
|
96
|
+
},
|
|
97
|
+
{ scope: rootRef },
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Playing a timeline that already exists creates nothing, so this needs no
|
|
102
|
+
* `contextSafe` — the context already owns it. Wrapping it would also be a
|
|
103
|
+
* lint error, because `contextSafe` runs during render and reading a ref
|
|
104
|
+
* there is what React forbids. Reserve `contextSafe` for a handler that
|
|
105
|
+
* *creates* a tween.
|
|
106
|
+
*/
|
|
107
|
+
const replay = useCallback(() => timelineRef.current?.restart(), []);
|
|
108
|
+
|
|
109
|
+
return { rootRef, replay };
|
|
110
|
+
}
|