[]>([]);
104 | const div3 = useRef(null);
105 | const trigger = useRef(null);
106 |
107 | useImperativeHandle(ref, () => ({
108 | div1,
109 | div2,
110 | div3,
111 | trigger,
112 | }));
113 |
114 | return (
115 |
116 |
THIS
117 | ) => div2.current.push(charRef)}
119 | wrapper={ }
120 | >
121 | TEST
122 |
123 | IS A
124 |
125 | );
126 | });
127 |
128 | You can then use the key of the exported refs in the `trigger` or `target` props.
129 | If it doesn't find a ref with this key it will use the `trigger` string as CSS selector.
130 |
131 |
132 |
140 | }>
141 |
147 |
153 |
160 |
161 |
162 |
163 |
164 | ## Standalone
165 |
166 | If you don't pass children to the component a GSAP ScrollTrigger instance will be created and can be used standalone.
167 |
168 | You can get the instance by calling `getGSAP()` on the ref.
169 |
170 |
171 | {() => {
172 | const scrollTrigger = React.useRef(null);
173 | return (
174 |
175 |
176 |
177 | console.log("toggled, isActive:", self.isActive)}
185 | onUpdate={self => {
186 | console.log("progress:", self.progress.toFixed(3), "direction:", self.direction, "velocity", self.getVelocity());
187 | }}
188 | />
189 | scrollTrigger.current.getGSAP().disable()}>Disable ScrollTrigger
190 |
191 | )
192 | }}
193 |
194 |
--------------------------------------------------------------------------------
/packages/docz/src/components/SplitChars.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: SplitChars
3 | menu: Components
4 | ---
5 |
6 | import { Fragment } from 'react';
7 | import { Playground, Props } from 'docz'
8 | import { Controls, PlayState, SplitChars } from './../../../react-gsap/src/'
9 | import { Tween } from './Tween'
10 |
11 | # SplitChars
12 |
13 | The SplitChars component is a small helper that splits a text by chars and returns one component per char.
14 |
15 | import { Controls, PlayState, Tween, SplitChars } from 'react-gsap';
16 |
17 | ## Usage with Tween
18 |
19 |
20 |
21 |
22 | }>
23 | This text gets splitted by chars.
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/packages/docz/src/components/SplitWords.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: SplitWords
3 | menu: Components
4 | ---
5 |
6 | import { Fragment } from 'react';
7 | import { Playground, Props } from 'docz'
8 | import { Controls, PlayState, SplitWords } from './../../../react-gsap/src/'
9 | import { Tween } from './Tween'
10 | import { Timeline } from './Timeline'
11 |
12 | # SplitWords
13 |
14 | The SplitWords component is a small helper that splits a text by words and returns one component per word.
15 |
16 | import { Controls, PlayState, Timeline, Tween, SplitWords } from 'react-gsap';
17 |
18 | ## Usage with Tween
19 |
20 |
21 |
22 |
23 | }>
24 | This text gets splitted by words.
25 |
26 |
27 |
28 |
29 |
30 | ## Usage with Timeline
31 |
32 |
33 |
34 |
36 | }>
37 | This text gets splitted by words.
38 |
39 |
40 | }>
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | ## With custom delimiter
49 |
50 | You can use the `delimiter` prop to use a custom delimiter string.
51 |
52 |
53 |
54 |
55 | }
58 | >
59 | This|text|gets|splitted|by|custom|delimiter
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/packages/docz/src/components/Timeline.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Timeline
3 | menu: Components
4 | ---
5 |
6 | import { Fragment } from 'react';
7 | import { Playground, Props } from 'docz'
8 | import { Controls, PlayState } from './../../../react-gsap/src/'
9 | import { Timeline, TimelinePropsDummy, TargetWithNames } from './Timeline'
10 | import { Tween } from './Tween'
11 |
12 | # Timeline
13 |
14 | The Timeline component uses the [gsap.timeline()](https://greensock.com/docs/v3/GSAP/Timeline) function internally.
15 |
16 | import { Controls, PlayState, Timeline, Tween } from 'react-gsap';
17 |
18 | ## Basic usage
19 |
20 | You can add a target and control it with childless Tween components. The target needs to be a "refable" component.
21 | So it can be a HTML element or a forwardRef component like a styled-components component.
22 |
23 |
24 |
25 |
28 | }
29 | >
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | ## Other Tweens
38 |
39 | You can also add other normal Tweens.
40 |
41 |
42 |
43 |
46 | }
47 | >
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | ## Multiple targets
58 |
59 | You can wrap multiple target components in a Fragment and target them with the array index with the `target` prop from the Tween component.
60 | If you don't add a target you transform all target components.
61 |
62 |
63 |
64 |
67 |
68 |
69 |
70 | }
71 | >
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | ## Advanced multiple targets
82 |
83 | If you need to target individual elements you can use a special forwardRef component with useImperativeHandle hook.
84 |
85 | In this way these component can be better reused and the refs not only work in a Timeline target context.
86 |
87 | You can also pass an array ref like seen with div2. In this way you can use the `stagger` prop.
88 |
89 | ```javascript
90 | const TargetWithNames = forwardRef((props, ref) => {
91 | const div1 = useRef(null);
92 | const div2 = useRef([]);
93 | const div3 = useRef(null);
94 | useImperativeHandle(ref, () => ({
95 | div1,
96 | div2,
97 | div3,
98 | }));
99 | return (
100 |
101 |
THIS
102 | div2.current.push(charRef)}
104 | wrapper={ }
105 | >
106 | TEST
107 |
108 | IS A
109 |
110 | );
111 | });
112 |
113 | ```
114 |
115 | If you want to combine multiple of those named components, you can do it like this:
116 |
117 | ```javascript
118 | const TargetWithNamesCombined = forwardRef((props, ref) => {
119 | const target1 = useRef({});
120 | const target2 = useRef({});
121 | useImperativeHandle(ref, () => ({
122 | ...target1.current,
123 | ...target2.current,
124 | }));
125 | return (
126 | <>
127 |
128 |
129 | >
130 | );
131 | });
132 |
133 | ```
134 |
135 | For version < 3:
136 |
137 | If you need to target individual elements you can use a special forwardRef function.
138 | The `targets` parameter provide the `set` function, which you can use to set a ref to a certain key.
139 |
140 | If you use an array as value, as seen in the example, you can save multiple elements as array under one key and use e.g. the `stagger` prop.
141 |
142 | ```javascript
143 | const TargetWithNames = forwardRef((props, targets) => (
144 |
145 |
targets.set('div1', div)}>THIS
146 | targets.set('div2', [div])}
148 | wrapper={ }
149 | >
150 | TEST
151 |
152 | targets.set('div3', div)}>IS A
153 |
154 | ));
155 |
156 | ```
157 |
158 |
159 |
160 | }>
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | ## Nested Timelines
169 |
170 | You can nest other Timelines or HTML structures.
171 |
172 |
173 |
174 |
177 | }
178 | >
179 |
180 |
181 |
182 |
Other Timeline:
183 |
186 | }
187 | >
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 | ## Label support
197 |
198 | You can add labels to the timeline and use them in the position prop of the Tweens or nested Timelines.
199 |
200 | More info: [https://greensock.com/docs/v3/GSAP/Timeline/addLabel()](https://greensock.com/docs/v3/GSAP/Timeline/addLabel())
201 |
202 |
203 |
204 |
207 | }
208 | labels={[
209 | {
210 | label: 'sec4',
211 | position: 4,
212 | },
213 | {
214 | label: 'sec6',
215 | position: 6,
216 | },
217 | ]}
218 | >
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 | ## Props
228 |
229 | The question mark means it's an optional prop.
230 |
231 | For all available props check out the documentation: [vars](https://greensock.com/docs/v3/GSAP/Timeline/vars)
232 |
233 | | Name | Type | Description |
234 | | :-- | :-- | :-- |
235 | | children | React.ReactNode | Can be any component type. But Tween and other Timeline components are controlled by this Timeline |
236 | | wrapper? | React.ReactElement | This component gets wrapped around the Timeline component |
237 | | target? | React.ReactElement \| null | The target component that gets outputted and tweened from all childless Tween child components |
238 | | position? | string \| number | If this Timeline is a child from another Timeline it's added at this position |
239 | | labels? | Label[] | Can be used to give the positions a name |
240 | | duration? | number | Adjusts the Timeline's timeScale to fit it within the specified duration (Can be changed on-the-fly) |
241 | | progress? | number | 0 - 1 (Can be changed on-the-fly) |
242 | | totalProgress? | number | 0 - 1 (Can be changed on-the-fly) |
243 | | playState? | PlayState | Use it to control the playing state (Can be changed on-the-fly) |
244 | | [prop: string] | any | All other props are added to the vars object for the gsap.timeline function |
245 |
246 | ## Type Label
247 |
248 | More info: [https://greensock.com/docs/v3/GSAP/Timeline/addLabel()](https://greensock.com/docs/v3/GSAP/Timeline/addLabel())
249 |
250 | | Name | Type |
251 | | :-- | :-- |
252 | | label | string |
253 | | position | string \| number |
254 |
255 | ## Enum PlayState
256 |
257 | | Field | As string |
258 | | :-- | :-- |
259 | | play | "play" |
260 | | restart | "restart" |
261 | | reverse | "reverse" |
262 | | restartReverse | "restartReverse" |
263 | | stop | "stop" |
264 | | stopEnd | "stopEnd" |
265 | | pause | "pause" |
266 | | resume | "resume" |
267 |
--------------------------------------------------------------------------------
/packages/docz/src/components/Timeline.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | forwardRef,
3 | MutableRefObject,
4 | ReactElement,
5 | useImperativeHandle,
6 | useRef,
7 | } from 'react';
8 | import Timeline, { TimelineProps } from './../../../react-gsap/src/Timeline';
9 | import { SplitChars } from './../../../react-gsap/src';
10 |
11 | export const TimelinePropsDummy: React.FunctionComponent = props => (
12 |
13 | );
14 |
15 | const TargetWithNames = forwardRef((props, ref: any) => {
16 | const div1 = useRef(null);
17 | const div2 = useRef[]>([]);
18 | const div3 = useRef(null);
19 | const trigger = useRef(null);
20 | useImperativeHandle(ref, () => ({
21 | div1,
22 | div2,
23 | div3,
24 | trigger,
25 | }));
26 | return (
27 |
28 |
THIS
29 | ) => div2.current.push(charRef)}
31 | wrapper={ }
32 | >
33 | TEST
34 |
35 | IS A
36 |
37 | );
38 | });
39 |
40 | export { Timeline, TargetWithNames };
41 |
--------------------------------------------------------------------------------
/packages/docz/src/components/Tween.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Tween
3 | menu: Components
4 | ---
5 |
6 | import { Fragment } from 'react'
7 | import { Playground, Props } from 'docz'
8 | import { Controls, PlayState } from './../../../react-gsap/src/'
9 | import { Tween, TweenPropsDummy, StaggerPropsDummy } from './Tween'
10 |
11 | # Tween
12 |
13 | The Tween component uses the [gsap.to()](https://greensock.com/docs/v3/GSAP/gsap.to()),
14 | [gsap.from()](https://greensock.com/docs/v3/GSAP/gsap.from()) and
15 | [gsap.fromTo()](https://greensock.com/docs/v3/GSAP/gsap.fromTo()) functions internally.
16 |
17 | The children need to be "refable" components. So they can be HTML elements or forwardRef components like styled-components components.
18 |
19 | import { Controls, PlayState, Tween } from 'react-gsap';
20 |
21 | ## Use "to" prop
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | ## Use "from" prop
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | ## Use "from" and "to" prop
42 |
43 | Internally the [gsap.fromTo()](https://greensock.com/docs/v3/GSAP/gsap.fromTo()) function will be called.
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | ## Use stagger prop
54 |
55 | If you have multiple children you can make use of the stagger prop.
56 |
57 | More info: https://greensock.com/docs/v3/Staggers
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | ### Advanced stagger
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | ## Props
92 |
93 | The question mark means it's an optional prop.
94 |
95 | For a documentation of all possible props or from and to values check out the documentations:
96 | [vars](https://greensock.com/docs/v3/GSAP/Tween/vars), [gsap.to()](https://greensock.com/docs/v3/GSAP/gsap.to()),
97 | [gsap.from()](https://greensock.com/docs/v3/GSAP/gsap.from()) and [gsap.fromTo()](https://greensock.com/docs/v3/GSAP/gsap.fromTo())
98 |
99 | | Name | Type | Description |
100 | | :-- | :-- | :-- |
101 | | children? | React.ReactNode | Need to be "refable" components. So they can be HTML elements or forwardRef components like styled-components components |
102 | | wrapper? | any | This component gets wrapped around the Tween component |
103 | | target? | number \| string | The target index or key of the Timeline targets. Used if Tween is childless and child of a Timeline |
104 | | position? | string \| number | If this Tween is a child from a Timeline it's added at this position |
105 | | from? | any | The vars object for the [gsap.from()](https://greensock.com/docs/v3/GSAP/gsap.from()) or [gsap.fromTo()](https://greensock.com/docs/v3/GSAP/gsap.fromTo()) function |
106 | | to? | any | The vars object for the [gsap.to()](https://greensock.com/docs/v3/GSAP/gsap.to()) or [gsap.fromTo()](https://greensock.com/docs/v3/GSAP/gsap.fromTo()) function |
107 | | stagger? | number \| Stagger \| (index: number, target: any, list: any) => number | If multiple children are added, you can stagger the start times for each |
108 | | duration? | number | The duration of the animation (in seconds) (Can be changed on-the-fly) |
109 | | progress? | number | 0 - 1 (Can be changed on-the-fly) |
110 | | totalProgress? | number | 0 - 1 (Can be changed on-the-fly) |
111 | | playState? | PlayState | Use it to control the playing state (Can be changed on-the-fly) |
112 | | disabled? | boolean | on-the-fly changes and are no more possible |
113 | | onlyInvalidateTo? | boolean | |
114 | | [prop: string] | any | All other props are added to the vars object for the Tween functions |
115 |
116 | ## Type Stagger
117 |
118 | More info: https://greensock.com/docs/v3/Staggers
119 |
120 | | Name | Type |
121 | | :-- | :-- |
122 | | amount? | number |
123 | | each? | number |
124 | | from? | 'start' \| 'center' \| 'edges' \| 'random' \| 'end' \| number \| [number, number] |
125 | | grid? | [number, number] \| 'auto' |
126 | | axis? | 'x' \| 'y' |
127 | | ease? | string \| (value: number) => number |
128 | | repeat? | number |
129 | | yoyo? | boolean |
130 | | [prop: string] | any |
131 |
132 | ## Enum PlayState
133 |
134 | | Field | As string |
135 | | :-- | :-- |
136 | | play | "play" |
137 | | restart | "restart" |
138 | | reverse | "reverse" |
139 | | restartReverse | "restartReverse" |
140 | | stop | "stop" |
141 | | stopEnd | "stopEnd" |
142 | | pause | "pause" |
143 | | resume | "resume" |
144 |
--------------------------------------------------------------------------------
/packages/docz/src/components/Tween.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Tween, { TweenProps, Stagger } from './../../../react-gsap/src/Tween';
3 |
4 | export const TweenPropsDummy: React.FunctionComponent = props =>
;
5 | export const StaggerPropsDummy: React.FunctionComponent = props =>
;
6 | export { Tween };
7 |
8 | export const Comment = (props: any) => {props.children}
;
9 |
--------------------------------------------------------------------------------
/packages/docz/src/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Introduction
3 | route: /
4 | ---
5 |
6 | [](https://www.npmjs.com/package/react-gsap)
7 | 
8 | 
9 |
10 | # Introduction
11 |
12 | `react-gsap` lets you use the GreenSock Animation Platform (GSAP) in React in a fully declarative way.
13 | It abstracts away the direct use of the GSAP [Tween](https://greensock.com/docs/v3/GSAP/Tween) and [Timeline](https://greensock.com/docs/v3/GSAP/Timeline) functions.
14 |
15 | If you need the full control it's possible by getting low level access to the underlying objects.
16 |
17 | In addition to that it ships some GSAP Plugins and useful helper components.
18 |
19 | From version 2 on it's build for GSAP 3 and only has `gsap` as a peer dependency. In this way you can update `gsap` separately from `react-gsap`.
20 |
21 | It's built with TypeScript and ships the types directly in the package.
22 |
23 | Documentation and examples are here: https://bitworking.github.io/react-gsap/
24 |
25 | ##### The examples on the documentation pages are all editable directly in the browser. So play with it!
26 |
27 | ## Installation
28 |
29 | ```bash
30 | npm install gsap react-gsap
31 | ```
32 |
33 | ## About GSAP
34 |
35 | GreenSock Animation Platform (GSAP) is a set of some JavaScript functions which let you tween a value/attribute/css property over time and insert these tweens into a timeline for more complex animations.
36 |
37 | `react-gsap` just adds some React wrapper components for these functions, so also read the official GreenSock documentation to know how to do things:
38 |
39 | [GreenSock Docs](https://greensock.com/docs/)
40 |
41 |
42 | ## License
43 |
44 | MIT © [bitworking](https://github.com/bitworking)
45 |
--------------------------------------------------------------------------------
/packages/docz/src/instructions/Easing.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Easing
3 | menu: Instructions
4 | ---
5 |
6 | import { Fragment } from 'react';
7 | import { Playground } from 'docz'
8 | import { Controls, PlayState, Tween } from './../../../react-gsap/src/'
9 |
10 | import { gsap } from 'gsap'
11 | import { SlowMo } from 'gsap/EasePack'
12 | gsap.registerPlugin(SlowMo)
13 |
14 | # Easing
15 |
16 | With GSAP 3 the easing functions are more easily usable as string without importing. They can be used in the "ease" prop of the `Tween`.
17 |
18 | Have a look at the docs: https://greensock.com/docs/v3/Eases
19 |
20 | The default is `power1.out`. You can overide the defaults if you want: https://greensock.com/docs/v3/GSAP/gsap.defaults().
21 |
22 | import { Controls, PlayState, Tween } from 'react-gsap';
23 |
24 | ## Ease function with parameters
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | ## SlowMo ease
35 |
36 | This is an extra ease that has to be registered first:
37 |
38 | import { gsap } from 'gsap';
39 | import { SlowMo } from 'gsap/EasePack';
40 |
41 | gsap.registerPlugin(SlowMo);
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | ## Custom easing function
52 |
53 | You can also pass a function to the ease prop. In this way you can have your custom easing functions.
54 |
55 | In the example it's an `easeInOutQuart`. Take a look at this resource for inspiration: https://easings.net/
56 |
57 |
58 |
59 | x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2}>
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/packages/docz/src/instructions/LowLevelAccess.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Low-level access
3 | menu: Instructions
4 | ---
5 |
6 | import { Fragment } from 'react';
7 | import { Playground } from 'docz'
8 | import { PlayState, Tween } from './../../../react-gsap/src/'
9 |
10 | # Low-level access
11 |
12 | You are able to use the complete API of the underlying GSAP objects.
13 | Just add a reference to the React `Tween` or `Timeline` components and get the `Tween` or `Timeline` objects from GSAP by calling the getGSAP() method on it:
14 |
15 | import { PlayState, Tween } from 'react-gsap';
16 |
17 | ### Usage with Tween
18 |
19 |
20 | {() => {
21 | const tween = React.useRef(null)
22 | return (
23 |
24 |
25 |
26 |
27 | tween.current.getGSAP().play(0)}>
28 | Play
29 |
30 | tween.current.getGSAP().seek(1)}>
31 | Seek
32 |
33 | tween.current.getGSAP().pause(0)}>
34 | Stop
35 |
36 |
37 | )
38 | }}
39 |
40 |
41 |
--------------------------------------------------------------------------------
/packages/docz/src/instructions/Migration.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Migration from version 1
3 | menu: Instructions
4 | ---
5 |
6 | import { Fragment } from 'react';
7 | import { Playground } from 'docz'
8 | import { PlayState, Tween } from './../../../react-gsap/src/'
9 |
10 | # Migration from version 1
11 |
12 | `react-gsap` version 2 is mostly backwards compatible with version 1.
13 |
14 | But there are 2 changes:
15 |
16 | 1. You need to install `gsap` separately. So if you upgrade `react-gsap` you need to upgrade `gsap` to a version >= 3.
17 |
18 | 2. The `Tween` component props `staggerFrom` and `staggerTo` are removed.
19 | In `gsap` version >=3 you can "stagger" the normal
20 | [gsap.to()](https://greensock.com/docs/v3/GSAP/gsap.to()), [gsap.from()](https://greensock.com/docs/v3/GSAP/gsap.from()) and
21 | [gsap.fromTo()](https://greensock.com/docs/v3/GSAP/gsap.fromTo()) functions.
22 |
23 | So replace this:
24 |
25 |
26 |
27 |
28 |
29 |
30 | With this:
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | There are possible more changes in `gsap` version 3 that leads to a different behaviour. Have also a look at https://greensock.com/3-migration/.
39 | If you find something please open an issue in github.
40 |
--------------------------------------------------------------------------------
/packages/docz/src/instructions/PlayState.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Use "playState" prop
3 | menu: Instructions
4 | ---
5 |
6 | import { Fragment } from 'react';
7 | import { Playground } from 'docz'
8 | import { PlayState, Tween } from './../../../react-gsap/src/'
9 |
10 | # Use playState and totalProgress props
11 |
12 | With the playState and progress/totalProgress props you can control a Tween or a Timeline.
13 | So you don't need low-level access to play/reverse/pause/stop or seek to a position.
14 |
15 | From version 3.2.0 on the `playState` prop also works for the initial state and the following states were added:
16 | `restartReverse`, `stopEnd`, `resume`.
17 |
18 | The following gsap functions are called internally, if the `playState` prop change:
19 |
20 | if (playState === PlayState.play) {
21 | gsap.play();
22 | } else if (playState === PlayState.restart) {
23 | gsap.restart(true);
24 | } else if (playState === PlayState.reverse) {
25 | gsap.reverse();
26 | } else if (playState === PlayState.restartReverse) {
27 | gsap.reverse(0);
28 | } else if (playState === PlayState.stop) {
29 | gsap.pause(0);
30 | } else if (playState === PlayState.stopEnd) {
31 | gsap.reverse(0);
32 | gsap.pause();
33 | } else if (playState === PlayState.pause) {
34 | gsap.pause();
35 | } else if (playState === PlayState.resume) {
36 | gsap.resume();
37 | }
38 |
39 |
40 | {() => {
41 | const [playState, setPlayState] = React.useState(PlayState.pause);
42 | const [totalProgress, setTotalProgress] = React.useState(0)
43 | return (
44 |
45 |
46 |
47 |
48 | setPlayState(PlayState.play)}>play
49 | setPlayState(PlayState.restart)}>restart
50 | setPlayState(PlayState.reverse)}>reverse
51 | setPlayState(PlayState.restartReverse)}>restartReverse
52 | setPlayState(PlayState.stop)}>stop
53 | setPlayState(PlayState.stopEnd)}>stopEnd
54 | setPlayState(PlayState.pause)}>pause
55 | setPlayState(PlayState.resume)}>resume
56 |
57 | setTotalProgress(event.target.value)} />
58 |
59 |
60 | )
61 | }}
62 |
63 |
64 |
--------------------------------------------------------------------------------
/packages/docz/src/plugins/CountPlugin.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: CountPlugin
3 | menu: Plugins
4 | ---
5 |
6 | import { Fragment } from 'react';
7 | import { Playground, Props } from 'docz'
8 | import { Controls, PlayState, Timeline, Tween } from './../../../react-gsap/src/'
9 |
10 | # CountPlugin
11 |
12 | The CountPlugin animates an number counting. It uses the innerText from the target elements. (children)
13 |
14 | It can be called with the "count" property which takes an number or an object with the following properties:
15 |
16 | | Name | Type |
17 | | :-- | :-- |
18 | | value | number |
19 | | format | () => (value) => string |
20 |
21 | import { Controls, PlayState, Tween } from 'react-gsap';
22 |
23 | ## Integer
24 |
25 | By default the count value will be parsed to an integer.
26 |
27 |
28 |
29 |
40 |
41 |
42 |
43 | ## Custom format function
44 |
45 | You can use your own custom format function.
46 |
47 |
48 |
49 |
50 |
(value) => value.toFixed(2),
55 | },
56 | }}
57 | ease="none"
58 | duration={5}
59 | >
60 | 10.00
61 |
62 |
63 |
64 |
65 |
66 | ## Percentage Bar
67 |
68 |
69 |
70 |
71 | (value) => `${parseInt(value, 10)} %`,
76 | },
77 | }}
78 | ease="none"
79 | duration={5}
80 | position={0}
81 | >
82 | 0 %
83 |
84 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/packages/docz/src/plugins/GSAP.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: GSAP Plugins
3 | menu: Plugins
4 | ---
5 |
6 | import { Fragment } from 'react';
7 | import { Playground } from 'docz'
8 | import { Controls, PlayState, Tween } from './../../../react-gsap/src/'
9 |
10 | import { gsap } from 'gsap'
11 | import { TextPlugin } from 'gsap/TextPlugin'
12 | gsap.registerPlugin(TextPlugin)
13 |
14 | # GSAP Plugins
15 |
16 | There are some Core Plugins that are already included in GSAP: https://greensock.com/docs/v3/GSAP/CorePlugins
17 |
18 | And there are some free Plugins that you can use to extend the functionality. Here is the list: https://greensock.com/docs/v3/Plugins.
19 |
20 | To use them with `react-gsap` you first have to register them.
21 |
22 | ### Register the TextPlugin for example:
23 |
24 | import { gsap } from 'gsap';
25 | import { TextPlugin } from 'gsap/TextPlugin';
26 |
27 | # if your build step doesn't transpile inside node_modules you can try this import:
28 | import { TextPlugin } from 'gsap/dist/TextPlugin';
29 |
30 | gsap.registerPlugin(TextPlugin);
31 |
32 | ### And use it:
33 |
34 |
35 |
36 |
37 | This is a text.
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/packages/docz/src/plugins/SvgDrawPlugin.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: SvgDrawPlugin
3 | menu: Plugins
4 | ---
5 |
6 | import { Fragment } from 'react';
7 | import { Playground, Props } from 'docz'
8 | import { Controls, PlayState, Tween } from './../../../react-gsap/src/'
9 |
10 | # SvgDrawPlugin
11 |
12 | The SvgDrawPlugin is shipped with react-gsap and lets you animate SVG elements.
13 | You can draw the following SVG elements: path, circle, rect, line, polyline and polygon.
14 |
15 | It works similar to the [DrawSVGPlugin](https://greensock.com/drawSVG) from GreenSock but the parameters are a little different.
16 |
17 | It can be called with the "svgDraw" property and takes a single number (0-1) value or an array with two numbers (Tuple) ([(0-1), (0-1)]).
18 |
19 | The single or first number is the length of the stroke.
20 |
21 | The second value is the position/offset on the path. (default = 0)
22 |
23 | So you can animate a line drawing from start to end like that:
24 |
25 | import { Controls, PlayState, Tween } from 'react-gsap';
26 |
27 | ## Single Number
28 |
29 |
30 |
31 |
38 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | ## Tuple
53 |
54 | An animation from the middle to the outside:
55 |
56 |
57 |
58 |
65 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | ## All possible SVG elements
80 |
81 |
82 |
83 |
84 |
93 |
102 |
113 |
114 |
115 |
116 |
122 |
128 |
129 |
130 |
131 |
132 |
133 |
--------------------------------------------------------------------------------
/packages/docz/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "moduleResolution": "node",
5 | "strict": true,
6 | "resolveJsonModule": true,
7 | "esModuleInterop": true,
8 | "skipLibCheck": false,
9 | "noEmit": true,
10 | "jsx": "react",
11 | "allowSyntheticDefaultImports": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/next/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
3 |
--------------------------------------------------------------------------------
/packages/next/README.md:
--------------------------------------------------------------------------------
1 | # TypeScript Next.js example
2 |
3 | This is a really simple project that shows the usage of Next.js with TypeScript.
4 |
5 | ## Deploy your own
6 |
7 | Deploy the example using [Vercel](https://vercel.com):
8 |
9 | [](https://vercel.com/import/project?template=https://github.com/zeit/next.js/tree/canary/examples/with-typescript)
10 |
11 | ## How to use it?
12 |
13 | ### Using `create-next-app`
14 |
15 | Execute [`create-next-app`](https://github.com/zeit/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
16 |
17 | ```bash
18 | npm init next-app --example with-typescript with-typescript-app
19 | # or
20 | yarn create next-app --example with-typescript with-typescript-app
21 | ```
22 |
23 | ### Download manually
24 |
25 | Download the example:
26 |
27 | ```bash
28 | curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-typescript
29 | cd with-typescript
30 | ```
31 |
32 | Install it and run:
33 |
34 | ```bash
35 | npm install
36 | npm run dev
37 | # or
38 | yarn
39 | yarn dev
40 | ```
41 |
42 | Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
43 |
44 | ## Notes
45 |
46 | This example shows how to integrate the TypeScript type system into Next.js. Since TypeScript is supported out of the box with Next.js, all we have to do is to install TypeScript.
47 |
48 | ```
49 | npm install --save-dev typescript
50 | ```
51 |
52 | To enable TypeScript's features, we install the type declarations for React and Node.
53 |
54 | ```
55 | npm install --save-dev @types/react @types/react-dom @types/node
56 | ```
57 |
58 | When we run `next dev` the next time, Next.js will start looking for any `.ts` or `.tsx` files in our project and builds it. It even automatically creates a `tsconfig.json` file for our project with the recommended settings.
59 |
60 | Next.js has built-in TypeScript declarations, so we'll get autocompletion for Next.js' modules straight away.
61 |
62 | A `type-check` script is also added to `package.json`, which runs TypeScript's `tsc` CLI in `noEmit` mode to run type-checking separately. You can then include this, for example, in your `test` scripts.
63 |
--------------------------------------------------------------------------------
/packages/next/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/packages/next/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | reactStrictMode: true,
3 | }
--------------------------------------------------------------------------------
/packages/next/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "with-typescript",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "dev": "next",
6 | "build": "next build",
7 | "start": "next start",
8 | "type-check": "tsc"
9 | },
10 | "dependencies": {
11 | "gsap": "^3.11.1",
12 | "next": "^12.3.0",
13 | "react": "^18.2.0",
14 | "react-dom": "^18.2.0",
15 | "react-gsap": "3.3.0"
16 | },
17 | "devDependencies": {
18 | "@types/node": "^18.7.18",
19 | "@types/react": "^18.0.20",
20 | "@types/react-dom": "^18.0.6",
21 | "typescript": "4.8.3"
22 | },
23 | "license": "ISC"
24 | }
25 |
--------------------------------------------------------------------------------
/packages/next/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { ReactElement, forwardRef } from 'react';
2 | import { SplitChars, Timeline, Tween, Controls, PlayState } from 'react-gsap';
3 |
4 | const IndexPage = () => (
5 | <>
6 |
7 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | >
33 | );
34 |
35 | export default IndexPage;
36 |
--------------------------------------------------------------------------------
/packages/next/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "alwaysStrict": true,
5 | "esModuleInterop": true,
6 | "forceConsistentCasingInFileNames": true,
7 | "isolatedModules": true,
8 | "jsx": "preserve",
9 | "lib": [
10 | "dom",
11 | "es2017"
12 | ],
13 | "module": "esnext",
14 | "moduleResolution": "node",
15 | "noEmit": true,
16 | "noFallthroughCasesInSwitch": true,
17 | "noUnusedLocals": true,
18 | "noUnusedParameters": true,
19 | "resolveJsonModule": true,
20 | "skipLibCheck": true,
21 | "strict": true,
22 | "target": "esnext",
23 | "incremental": true
24 | },
25 | "exclude": [
26 | "node_modules"
27 | ],
28 | "include": [
29 | "**/*.ts",
30 | "**/*.tsx"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/packages/playground/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `yarn start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `yarn test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `yarn build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `yarn eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
--------------------------------------------------------------------------------
/packages/playground/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-gsap-playground",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.5",
7 | "@testing-library/react": "^13.4.0",
8 | "@testing-library/user-event": "^14.4.3",
9 | "@types/jest": "^29.0.3",
10 | "@types/node": "^18.7.18",
11 | "@types/react": "^18.0.20",
12 | "@types/react-dom": "^18.0.6",
13 | "@types/react-router-dom": "^5.3.3",
14 | "@types/react-transition-group": "^4.4.5",
15 | "@types/styled-components": "^5.1.26",
16 | "@types/uuid": "^8.3.4",
17 | "gsap": "^3.11.1",
18 | "react": "^18.2.0",
19 | "react-dom": "^18.2.0",
20 | "react-gsap": "3.3.0",
21 | "react-router-dom": "^6.4.0",
22 | "react-scripts": "5.0.1",
23 | "react-transition-group": "^4.4.5",
24 | "styled-components": "^5.3.5",
25 | "typescript": "~4.8.3",
26 | "uuid": "^9.0.0"
27 | },
28 | "scripts": {
29 | "start": "react-scripts start",
30 | "build": "react-scripts build",
31 | "test": "react-scripts test",
32 | "eject": "react-scripts eject"
33 | },
34 | "eslintConfig": {
35 | "extends": "react-app"
36 | },
37 | "browserslist": {
38 | "production": [
39 | ">0.2%",
40 | "not dead",
41 | "not op_mini all"
42 | ],
43 | "development": [
44 | "last 1 chrome version",
45 | "last 1 firefox version",
46 | "last 1 safari version"
47 | ]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/playground/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitworking/react-gsap/b6180f5a3ccd243611c8e891c8932dbf3b1d1d87/packages/playground/public/favicon.ico
--------------------------------------------------------------------------------
/packages/playground/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/packages/playground/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitworking/react-gsap/b6180f5a3ccd243611c8e891c8932dbf3b1d1d87/packages/playground/public/logo192.png
--------------------------------------------------------------------------------
/packages/playground/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bitworking/react-gsap/b6180f5a3ccd243611c8e891c8932dbf3b1d1d87/packages/playground/public/logo512.png
--------------------------------------------------------------------------------
/packages/playground/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/packages/playground/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/packages/playground/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from '@testing-library/react';
3 | import App from './App';
4 |
5 | test('renders learn react link', () => {
6 | const { getByText } = render( );
7 | const linkElement = getByText(/learn react/i);
8 | expect(linkElement).toBeInTheDocument();
9 | });
10 |
--------------------------------------------------------------------------------
/packages/playground/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
3 |
4 | import Tween from './examples/Tween';
5 | import Timeline from './examples/Timeline';
6 | import Svg from './examples/Svg';
7 | import Transition from './examples/Transition';
8 | import Reveal from './examples/Reveal';
9 | import ScrollTrigger from './examples/ScrollTrigger';
10 | import ManualControl from './examples/ManualControl';
11 | // import Scroller from './examples/Scroller';
12 |
13 | function App() {
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 | Tween
22 |
23 |
24 | Timeline
25 |
26 |
27 | Svg
28 |
29 |
30 | Transition
31 |
32 |
33 | Reveal
34 |
35 |
36 | ScrollTrigger
37 |
38 |
39 | ManualControl
40 |
41 | {/*
42 | Scroller
43 | */}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | {/*
*/}
57 |
58 |
59 |
60 | );
61 | }
62 |
63 | export default App;
64 |
--------------------------------------------------------------------------------
/packages/playground/src/examples/Animation.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment, ReactComponentElement, useEffect, useRef, useState } from 'react';
2 | import { SplitChars, SplitWords, Timeline, Tween } from 'react-gsap';
3 | import { gsap } from 'gsap';
4 |
5 | export const FadeIn = ({
6 | children,
7 | ...rest
8 | }: {
9 | children: React.ReactNode;
10 | [key: string]: any;
11 | }) => (
12 |
13 | {children}
14 |
15 | );
16 |
17 | export const FadeInLeft = ({
18 | children,
19 | ...rest
20 | }: {
21 | children: React.ReactNode;
22 | [key: string]: any;
23 | }) => (
24 |
29 | {children}
30 |
31 | );
32 |
33 | export const RubberBand = ({
34 | children,
35 | ...rest
36 | }: {
37 | children: React.ReactElement;
38 | [key: string]: any;
39 | }) => (
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | );
49 |
50 | export const FadeInLeftChars = ({
51 | children,
52 | wrapper,
53 | ...rest
54 | }: {
55 | children: React.ReactNode;
56 | wrapper: ReactComponentElement;
57 | [key: string]: any;
58 | }) => (
59 |
60 | {children}
61 |
62 | );
63 |
64 | export const FadeInLeftWords = ({
65 | children,
66 | wrapper,
67 | ...rest
68 | }: {
69 | children: React.ReactNode;
70 | wrapper: ReactComponentElement;
71 | [key: string]: any;
72 | }) => (
73 |
74 | {children}
75 |
76 | );
77 |
78 | export const CutText = ({
79 | children,
80 | numberSlices = 4,
81 | type = 0,
82 | ...rest
83 | }: {
84 | children: string;
85 | numberSlices?: number;
86 | type?: number;
87 | [key: string]: any;
88 | }) => {
89 | const textRef = useRef(null);
90 | const [viewBox, setViewBox] = useState({ x: 0, y: 0, width: 0, height: 0 });
91 | const [sliceHeight, setSliceHeight] = useState(0);
92 |
93 | useEffect(() => {
94 | const boundingBox = textRef.current
95 | ? textRef.current.getBBox()
96 | : { x: 0, y: 0, width: 0, height: 0 };
97 | const { x, y, width, height } = boundingBox;
98 | setViewBox({ x, y, width, height });
99 |
100 | setSliceHeight(height / numberSlices);
101 | }, []);
102 |
103 | return (
104 |
110 |
111 |
119 |
120 | {children}
121 |
122 |
123 |
124 | }
126 | target={
127 |
128 | {Array.from({ length: numberSlices }).map((_, index) => (
129 |
136 | ))}
137 |
138 | }
139 | {...rest}
140 | >
141 | {type === 0 && (
142 |
149 | )}
150 |
151 | {type === 1 && (
152 |
164 | )}
165 | {type === 2 && (
166 |
177 | )}
178 |
179 |
180 | );
181 | };
182 |
--------------------------------------------------------------------------------
/packages/playground/src/examples/ManualControl.tsx:
--------------------------------------------------------------------------------
1 | import React, { useImperativeHandle, useMemo, useRef } from 'react';
2 | // import './styles.css';
3 | import { PlayState, Timeline, Tween } from 'react-gsap';
4 | import styled from 'styled-components';
5 |
6 | // const CurriedForwardedTimelineTarget = React.forwardRef(
7 | // ({ text }: any, ref: any): JSX.Element => {
8 | // const Kekeeeeee = useRef(null);
9 | // useImperativeHandle(ref, () => ({
10 | // Kekeeeeee,
11 | // }));
12 | //
13 | // return (
14 | //
15 | //
{text}
16 | //
17 | // );
18 | // }
19 | // );
20 |
21 | const CurriedForwardedTimelineTarget: ({
22 | text,
23 | }: {
24 | text: string;
25 | }) => React.ForwardRefExoticComponent> = ({ text }: { text: string }) =>
26 | React.forwardRef(
27 | (_props: any, ref: any): JSX.Element => {
28 | const Kekeeeeee = useRef(null);
29 | useImperativeHandle(ref, () => ({
30 | Kekeeeeee,
31 | }));
32 | return (
33 |
34 |
{text}
35 |
36 | );
37 | }
38 | );
39 |
40 | const StyledH2 = styled.h2`
41 | color: red;
42 | `;
43 |
44 | export function InlineTimeline({ position }: { position: string | number }) {
45 | let h2Ref: React.MutableRefObject = React.useRef(null);
46 |
47 | return (
48 |
49 |
50 |
51 | Sliiiide Inline
52 |
53 |
54 |
55 | );
56 | }
57 |
58 | const ForwardedTimelineTarget: React.ForwardRefExoticComponent> = React.forwardRef(
61 | (_props: any, ref: any): JSX.Element => {
62 | const Sliiiide = useRef(null);
63 | useImperativeHandle(ref, () => ({
64 | Sliiiide,
65 | }));
66 | return (
67 |
68 |
Sliiiide Forwarded
69 |
70 | );
71 | }
72 | );
73 |
74 | export function ForwardedTimeline({ position }: { position: string | number }) {
75 | return (
76 | <>
77 | }
79 | position={position}
80 | // playState={playing ? PlayState.play : PlayState.pause}
81 | // totalProgress={progress}
82 | // paused={true}
83 | >
84 |
91 |
92 |
93 |
94 | >
95 | );
96 | }
97 |
98 | export function CurriedForwardedTimeline({
99 | position,
100 | text,
101 | totalProgress,
102 | }: {
103 | position: string | number;
104 | text: string;
105 | totalProgress?: number;
106 | }) {
107 | const CurriedForwardedTimelineTargetMarkup: React.ForwardRefExoticComponent> = useMemo(() => CurriedForwardedTimelineTarget({ text }), [text]);
110 |
111 | // const CurriedForwardedTimelineTargetMarkup: React.ForwardRefExoticComponent> = CurriedForwardedTimelineTarget({ text });
114 |
115 | return (
116 | <>
117 | }
119 | target={ }
120 | position={position}
121 | // playState={playing ? PlayState.play : PlayState.pause}
122 | // totalProgress={totalProgress}
123 | // paused={true}
124 | >
125 |
132 |
133 |
134 |
135 | >
136 | );
137 | }
138 |
139 | export default function App() {
140 | const [playState, setPlayState] = React.useState(PlayState.pause);
141 | const [progress, setProgress] = React.useState(0);
142 |
143 | return (
144 | <>
145 | {/**/}
146 |
147 | Tween
148 |
149 |
159 |
160 | {/* */}
161 | {/* */}
162 |
163 |
164 |
165 | {/* */}
166 | setPlayState(PlayState.play)}>play
167 | setPlayState(PlayState.restart)}>restart
168 | setPlayState(PlayState.reverse)}>reverse
169 | setPlayState(PlayState.restartReverse)}>restartReverse
170 | setPlayState(PlayState.stop)}>stop
171 | setPlayState(PlayState.stopEnd)}>stopEnd
172 | setPlayState(PlayState.pause)}>pause
173 | setPlayState(PlayState.resume)}>resume
174 |
175 | setProgress(parseFloat(event.target.value))}
182 | />
183 |
184 | >
185 | );
186 | }
187 |
--------------------------------------------------------------------------------
/packages/playground/src/examples/Reveal.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef } from 'react';
2 | import styled from 'styled-components';
3 | import { Reveal } from 'react-gsap';
4 | import {
5 | FadeIn,
6 | FadeInLeft,
7 | FadeInLeftChars,
8 | FadeInLeftWords,
9 | RubberBand,
10 | CutText,
11 | } from './Animation';
12 |
13 | const RevealStyled = styled.div`
14 | padding-top: 1000px;
15 | padding-bottom: 1000px;
16 | overflow: hidden;
17 |
18 | text-align: center;
19 | font-family: arial;
20 | font-size: 80px;
21 |
22 | h1 {
23 | font-size: 80px;
24 | font-weight: normal;
25 | margin: 0;
26 | padding: 60px 0;
27 | }
28 |
29 | svg {
30 | padding: 60px 0;
31 | }
32 | `;
33 |
34 | const Wrapper = forwardRef((props, ref: any) => {props.children}
);
35 |
36 | const RevealComponent = () => (
37 |
38 |
39 |
40 | REACT-GSAP
41 |
42 |
43 | }>
44 |
45 | AIIIIIIGHT
46 |
47 |
48 |
49 |
50 | ONE MORE
51 |
52 |
53 | }>
54 | }>
55 | SPLIT IT UP
56 |
57 |
58 | }>
59 | }>
60 | SPLIT IT UP
61 |
62 |
63 | }>
64 |
65 | CUT ME PLEASE
66 |
67 |
68 | }>
69 |
70 | CUT ME PLEASE
71 |
72 |
73 | }>
74 |
75 | CUT ME PLEASE
76 |
77 |
78 |
79 | );
80 |
81 | export default RevealComponent;
82 |
--------------------------------------------------------------------------------
/packages/playground/src/examples/ScrollTrigger.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | forwardRef,
3 | MutableRefObject,
4 | useEffect,
5 | useImperativeHandle,
6 | useRef,
7 | useState,
8 | } from 'react';
9 | import styled from 'styled-components';
10 | import {
11 | Tween,
12 | Timeline,
13 | SplitWords,
14 | SplitLetters,
15 | Controls,
16 | ScrollTrigger,
17 | PlayState,
18 | SplitChars,
19 | } from 'react-gsap';
20 |
21 | const Container = styled.div`
22 | width: 100%;
23 | height: 100vh;
24 | position: relative;
25 | `;
26 |
27 | const Square = styled.div`
28 | background-color: red;
29 | width: 100px;
30 | height: 100px;
31 | position: relative;
32 | `;
33 |
34 | const TweenStyled = styled.div`
35 | padding: 2000px 0;
36 | `;
37 |
38 | const FadeIn = ({ children }: { children: React.ReactNode }) => (
39 | {children}
40 | );
41 |
42 | const TargetWithNames = forwardRef((props, ref: any) => {
43 | const div1 = useRef(null);
44 | const div2 = useRef[]>([]);
45 | const div3 = useRef(null);
46 | const trigger = useRef(null);
47 | useImperativeHandle(ref, () => ({
48 | div1,
49 | div2,
50 | div3,
51 | trigger,
52 | }));
53 | return (
54 |
55 |
first
56 |
) => div2.current.push(charRef)}
58 | wrapper={ }
59 | >
60 | second
61 |
62 |
third
63 |
64 | );
65 | });
66 |
67 | const TweenComponent = () => {
68 | const triggerRef = useRef(null);
69 | const [trigger, setTrigger] = useState(triggerRef.current);
70 |
71 | useEffect(() => {
72 | setTrigger(triggerRef.current);
73 | }, []);
74 |
75 | return (
76 | <>
77 |
78 |
79 |
84 | This element gets tweened
85 |
86 |
87 |
88 |
89 |
94 | This element gets tweened by ref
95 |
96 |
97 |
98 |
106 |
111 |
112 | This element is the trigger
113 |
114 |
115 |
116 |
117 |
118 |
123 | This element gets tweened
124 |
125 |
130 | This element gets tweened
131 |
132 |
133 |
134 |
142 | }>
143 |
149 |
156 |
162 |
163 |
164 |
165 |
166 |
167 | {/**/}
168 | {/* */}
181 | {/* This element gets tweened */}
182 | {/* */}
183 | {/* */}
184 |
185 | >
186 | );
187 | };
188 |
189 | export default TweenComponent;
190 |
--------------------------------------------------------------------------------
/packages/playground/src/examples/Scroller.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | Fragment,
3 | ReactComponentElement,
4 | ReactHTMLElement,
5 | useEffect,
6 | useRef,
7 | useState,
8 | } from 'react';
9 | import styled from 'styled-components';
10 | import { Tween, Timeline, SplitChars, SplitWords } from 'react-gsap';
11 | // import { Scroller } from 'react-gsap';
12 | import { gsap } from 'gsap';
13 | import { CutText } from './Animation';
14 |
15 | const RevealStyled = styled.div`
16 | overflow: hidden;
17 | margin-bottom: 100vh;
18 | margin-top: 100vh;
19 |
20 | text-align: center;
21 | font-family: arial;
22 | font-size: 80px;
23 |
24 | h1 {
25 | font-size: 80px;
26 | font-weight: normal;
27 | margin: 0;
28 | padding: 60px 0;
29 | }
30 |
31 | svg {
32 | padding: 60px 0;
33 | }
34 |
35 | .fixed {
36 | position: fixed;
37 | top: 0;
38 | bottom: 0;
39 | left: 0;
40 | right: 0;
41 | display: flex;
42 | justify-content: center;
43 | align-items: center;
44 | }
45 | `;
46 |
47 | /*
48 | 0.3 && progress < 0.7 ? 'fixed' : undefined}>
49 |
50 | SCROLLREVEAL RULES
51 |
52 | {progress}
53 |
54 | */
55 |
56 | const ScrollerComponent = () => (
57 |
58 | {/*
59 | {(progress: number) => {progress}
}
60 | */}
61 | {/*
62 |
63 | {(progress: number) => (
64 | <>
65 | {progress >= 0 && (
66 |
67 |
68 | SCROLLREVEAL RULES
69 |
70 |
71 | )}
72 | >
73 | )}
74 |
75 | */}
76 |
77 | );
78 |
79 | export default ScrollerComponent;
80 |
--------------------------------------------------------------------------------
/packages/playground/src/examples/Svg.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { Tween, Timeline, Controls } from 'react-gsap';
4 |
5 | const SvgStyled = styled.div``;
6 |
7 | const SvgComponent = () => (
8 |
9 | Play with these example on{' '}
10 |
11 | StackBlitz.io
12 |
13 | SvgDraw PlugIn
14 |
15 | }
17 | from={{
18 | svgDraw: 0,
19 | }}
20 | to={{
21 | svgDraw: 1,
22 | }}
23 | duration={2}
24 | >
25 |
34 |
45 |
46 |
47 |
48 |
49 |
50 |
56 |
57 |
63 |
64 |
65 |
66 |
67 |
68 |
77 | }
78 | target={
79 |
80 | }
81 | duration={2}
82 | >
83 |
92 |
98 |
103 |
108 |
113 |
118 |
119 |
120 |
121 |
130 | }
131 | from={{
132 | svgDraw: 0,
133 | }}
134 | to={{
135 | svgDraw: 1,
136 | }}
137 | duration={5}
138 | >
139 |
145 |
146 |
147 |
148 | );
149 |
150 | export default SvgComponent;
151 |
--------------------------------------------------------------------------------
/packages/playground/src/examples/Timeline.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | forwardRef,
3 | Fragment,
4 | useRef,
5 | useImperativeHandle,
6 | ReactElement,
7 | ReactHTMLElement,
8 | useCallback,
9 | useEffect,
10 | MutableRefObject,
11 | } from 'react';
12 | import styled from 'styled-components';
13 | import { Tween, Timeline, SplitWords, SplitChars, Controls, PlayState } from 'react-gsap';
14 |
15 | const TimelineStyled = styled.div``;
16 |
17 | const StyledTarget1 = styled.div`
18 | height: 200px;
19 | background-color: #accef7;
20 | `;
21 |
22 | const StyledTarget2 = styled.div`
23 | height: 50px;
24 | background-color: #ff4757;
25 | padding: 50px;
26 | `;
27 |
28 | const Inline = styled.div`
29 | display: inline-block;
30 | font-size: 40px;
31 | `;
32 |
33 | const TimelinePlayState = () => {
34 | const [playing, setPlaying] = React.useState(false);
35 | const [progress, setProgress] = React.useState(0);
36 |
37 | return (
38 | <>
39 | }
41 | playState={playing ? PlayState.play : PlayState.pause}
42 | totalProgress={progress}
43 | paused={false}
44 | >
45 |
46 |
47 |
48 |
49 | setPlaying(!playing)}>{playing ? 'Pause' : 'Play'}
50 |
51 | setProgress(parseFloat(event.target.value))}
58 | />
59 |
60 | >
61 | );
62 | };
63 |
64 | const TimelineComponent = () => (
65 |
66 | Play with these example on{' '}
67 |
68 | StackBlitz.io
69 |
70 | Nested Timeline
71 |
72 |
83 | }
84 | target={
85 |
86 |
89 | }
90 | >
91 | This is a Timeline
92 |
93 |
94 | }
95 | repeat={0}
96 | >
97 |
104 |
105 |
112 | }
114 | target={
115 |
116 | }>
117 | AIIIIGHHT
118 |
119 |
120 | }
121 | labels={[
122 | {
123 | label: 'sec4',
124 | position: 4,
125 | },
126 | {
127 | label: 'sec6',
128 | position: 6,
129 | },
130 | ]}
131 | >
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 | );
142 |
143 | const TargetWithNames = forwardRef((props, ref: any) => {
144 | const div1 = useRef(null);
145 | const div2 = useRef[]>([]);
146 | const div3 = useRef(null);
147 | useImperativeHandle(ref, () => ({
148 | div1,
149 | div2,
150 | div3,
151 | }));
152 | return (
153 |
154 |
first
155 |
) => div2.current.push(charRef)}
157 | wrapper={ }
158 | >
159 | second
160 |
161 |
third
162 |
163 | );
164 | });
165 |
166 | const TargetWithNames2 = forwardRef((props, ref: any) => {
167 | const div4 = useRef(null);
168 | const div5 = useRef[]>([]);
169 | const div6 = useRef(null);
170 | useImperativeHandle(ref, () => ({
171 | div4,
172 | div5,
173 | div6,
174 | }));
175 | return (
176 |
177 |
first
178 |
) => div5.current.push(charRef)}
180 | wrapper={ }
181 | >
182 | second
183 |
184 |
third
185 |
186 | );
187 | });
188 |
189 | const TargetWithNamesCombined = forwardRef((props, ref: any) => {
190 | const target1 = useRef({});
191 | const target2 = useRef({});
192 | useImperativeHandle(ref, () => ({
193 | ...target1.current,
194 | ...target2.current,
195 | }));
196 | return (
197 | <>
198 |
199 |
200 | >
201 | );
202 | });
203 |
204 | const TimelineTargets = () => {
205 | return (
206 | }>
207 |
208 |
209 |
210 |
211 | );
212 | };
213 |
214 | //export default TimelineTargets;
215 |
216 | const ForwardRefComponent = forwardRef(({ children }, ref: any) => {
217 | return (
218 |
219 |
220 | {children}
221 |
222 |
223 | );
224 | });
225 |
226 | const Component = forwardRef(({ children }, ref?) => {
227 | const div1 = useRef(null);
228 | const div2 = useRef(null);
229 | useImperativeHandle(ref, () => ({
230 | div1,
231 | div2,
232 | test: () => {
233 | console.log('run test');
234 | },
235 | }));
236 | return (
237 |
238 |
239 | {children}
240 |
241 |
Div 2
242 |
243 | );
244 | });
245 |
246 | const AnimatedComponent = ({ children, ...props }) => {
247 | const component = useRef(null);
248 |
249 | useEffect(() => {
250 | console.log('component', component);
251 | }, []);
252 |
253 | return (
254 |
257 | {children}
258 |
259 | }
260 | >
261 |
262 |
263 |
264 | );
265 | };
266 |
267 | // const Out = () => {
268 | // return (
269 | // <>
270 | // Not animated
271 | // Animated
272 | // >
273 | // );
274 | // };
275 |
276 | const Out = () => {
277 | const divRef1 = useCallback(ref => {
278 | if (ref !== null) {
279 | // Ref never updates
280 | // console.log(ref);
281 | }
282 | }, []);
283 |
284 | const divRef2 = useRef(null);
285 |
286 | useEffect(() => {
287 | // Ref never updates
288 | // console.log(divRef2.current);
289 | }, []);
290 |
291 | return (
292 |
293 |
296 | //
297 | //
298 | //
299 | //
300 | //
301 | //
302 | // >
303 | // }
304 | // target={
}
305 | // target={ }
306 | target={
307 | <>
308 |
309 |
310 | >
311 | }
312 | // target={ }
313 | // target={
314 | // <>
315 | //
316 | // ForwardRefComponent 1
317 | // ForwardRefComponent 2
318 | // >
319 | // }
320 | >
321 | {/**/}
322 | {/*
*/}
323 | {/* */}
324 |
325 | {/**/}
326 | {/*
*/}
327 | {/* */}
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 | {/* */}
338 | {/* */}
339 |
340 |
341 | );
342 | };
343 |
344 | //export default Out;
345 |
346 | const Test = () => {
347 | // the array gets filled up with every new render!
348 | // can SplitWords outputs it's refs as array, so that we don't need to push into?
349 | const ref = useRef[]>([]);
350 |
351 | useEffect(() => {
352 | console.log(ref);
353 | }, []);
354 |
355 | return (
356 |
357 |
360 | ref.current.push(charRef)} wrapper={ }>
361 | This text gets splitted by words.
362 |
363 |
364 | }
365 | >
366 |
367 |
368 |
369 |
370 |
371 | );
372 | };
373 |
374 | export default Test;
375 |
--------------------------------------------------------------------------------
/packages/playground/src/examples/Transition.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { Tween, Timeline, PlayState } from 'react-gsap';
4 | import { Transition, TransitionGroup } from 'react-transition-group';
5 | import { v4 as uuid } from 'uuid';
6 |
7 | const Item = styled.div`
8 | background-color: #f0f0f0;
9 | border: 1px solid #999;
10 | margin-bottom: 5px;
11 | padding: 10px;
12 | cursor: pointer;
13 | `;
14 |
15 | const TransitionItem = ({
16 | children,
17 | onClick,
18 | ...props
19 | }: {
20 | children: React.ReactNode;
21 | onClick: Function;
22 | }) => (
23 |
24 | {state => (
25 |
26 |
46 | - onClick()}>
47 | {children} {state}
48 |
49 |
50 |
51 | )}
52 |
53 | );
54 |
55 | const TransitionStyled = styled.div`
56 | button {
57 | margin-bottom: 30px;
58 | }
59 |
60 | span {
61 | margin-left: 30px;
62 | }
63 | `;
64 |
65 | type State = {
66 | items: { id: string; text: string | number }[];
67 | };
68 |
69 | class TransitionComponent extends React.Component<{}, State> {
70 | state = {
71 | items: [{ id: uuid(), text: '0' }],
72 | };
73 |
74 | removeItem(id: string) {
75 | this.setState(state => ({
76 | items: state.items.filter(item => item.id !== id),
77 | }));
78 | }
79 |
80 | render() {
81 | const { items } = this.state;
82 |
83 | return (
84 |
85 | Play with these example on{' '}
86 |
87 | StackBlitz.io
88 |
89 | Mount/Unmount Animation - built with React Transition Group
90 | {
93 | this.setState(state => ({
94 | items: [...state.items, { id: uuid(), text: state.items.length }],
95 | }));
96 | }}
97 | >
98 | Add Item
99 |
100 | Click on item to remove
101 |
102 | {items.map(({ id, text }) => (
103 | this.removeItem(id)}>
104 | {id}
105 |
106 | ))}
107 |
108 |
109 | );
110 | }
111 | }
112 |
113 | export default TransitionComponent;
114 |
--------------------------------------------------------------------------------
/packages/playground/src/examples/Tween.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { Tween, SplitWords, SplitLetters, Controls } from 'react-gsap';
4 |
5 | const Square = styled.div`
6 | background-color: red;
7 | width: 100px;
8 | height: 100px;
9 | position: relative;
10 | `;
11 |
12 | const TweenStyled = styled.div``;
13 |
14 | const FadeIn = ({ children }: { children: React.ReactNode }) => (
15 | {children}
16 | );
17 |
18 | const TweenComponent = () => (
19 |
20 | Play with these examples on{' '}
21 |
22 | StackBlitz.io
23 |
24 | Basic tween
25 |
26 |
27 | This element gets tweened
28 |
29 |
30 | FadeIn component
31 |
32 | FadeIn Test
33 |
34 | SplitWords component
35 |
36 |
37 | }>
38 | This is a Test
39 |
40 |
41 |
42 | SplitLetters component + staggerFrom + ease
43 |
44 |
45 | }>
46 | This is a Test
47 |
48 |
49 |
50 | Nice list animation
51 |
52 | }
54 | from={{
55 | opacity: 0,
56 | rotationX: (index: number) => (index % 2 ? -90 : 90),
57 | transformOrigin: (index: number) => (index % 2 ? '50% top -100' : '50% bottom 100'),
58 | }}
59 | duration={1}
60 | stagger={0.1}
61 | >
62 | Rich Harris
63 | Dan Abramov
64 | Kyle Simpson
65 | Gregory Brown
66 | Addy Osmani
67 | Evan You
68 | Axel Rauschmayer
69 | Sarah Drasner
70 | André Staltz
71 |
72 |
73 | Styled components
74 |
75 |
84 |
85 |
86 |
87 |
88 |
89 | );
90 |
91 | export default TweenComponent;
92 |
--------------------------------------------------------------------------------
/packages/playground/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import * as serviceWorker from './serviceWorker';
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root')
11 | );
12 |
13 | // If you want your app to work offline and load faster, you can change
14 | // unregister() to register() below. Note this comes with some pitfalls.
15 | // Learn more about service workers: https://bit.ly/CRA-PWA
16 | serviceWorker.unregister();
17 |
--------------------------------------------------------------------------------
/packages/playground/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/packages/playground/src/serviceWorker.ts:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.0/8 are considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | type Config = {
24 | onSuccess?: (registration: ServiceWorkerRegistration) => void;
25 | onUpdate?: (registration: ServiceWorkerRegistration) => void;
26 | };
27 |
28 | export function register(config?: Config) {
29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
30 | // The URL constructor is available in all browsers that support SW.
31 | const publicUrl = new URL(
32 | process.env.PUBLIC_URL,
33 | window.location.href
34 | );
35 | if (publicUrl.origin !== window.location.origin) {
36 | // Our service worker won't work if PUBLIC_URL is on a different origin
37 | // from what our page is served on. This might happen if a CDN is used to
38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
39 | return;
40 | }
41 |
42 | window.addEventListener('load', () => {
43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
44 |
45 | if (isLocalhost) {
46 | // This is running on localhost. Let's check if a service worker still exists or not.
47 | checkValidServiceWorker(swUrl, config);
48 |
49 | // Add some additional logging to localhost, pointing developers to the
50 | // service worker/PWA documentation.
51 | navigator.serviceWorker.ready.then(() => {
52 | console.log(
53 | 'This web app is being served cache-first by a service ' +
54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
55 | );
56 | });
57 | } else {
58 | // Is not localhost. Just register service worker
59 | registerValidSW(swUrl, config);
60 | }
61 | });
62 | }
63 | }
64 |
65 | function registerValidSW(swUrl: string, config?: Config) {
66 | navigator.serviceWorker
67 | .register(swUrl)
68 | .then(registration => {
69 | registration.onupdatefound = () => {
70 | const installingWorker = registration.installing;
71 | if (installingWorker == null) {
72 | return;
73 | }
74 | installingWorker.onstatechange = () => {
75 | if (installingWorker.state === 'installed') {
76 | if (navigator.serviceWorker.controller) {
77 | // At this point, the updated precached content has been fetched,
78 | // but the previous service worker will still serve the older
79 | // content until all client tabs are closed.
80 | console.log(
81 | 'New content is available and will be used when all ' +
82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
83 | );
84 |
85 | // Execute callback
86 | if (config && config.onUpdate) {
87 | config.onUpdate(registration);
88 | }
89 | } else {
90 | // At this point, everything has been precached.
91 | // It's the perfect time to display a
92 | // "Content is cached for offline use." message.
93 | console.log('Content is cached for offline use.');
94 |
95 | // Execute callback
96 | if (config && config.onSuccess) {
97 | config.onSuccess(registration);
98 | }
99 | }
100 | }
101 | };
102 | };
103 | })
104 | .catch(error => {
105 | console.error('Error during service worker registration:', error);
106 | });
107 | }
108 |
109 | function checkValidServiceWorker(swUrl: string, config?: Config) {
110 | // Check if the service worker can be found. If it can't reload the page.
111 | fetch(swUrl, {
112 | headers: { 'Service-Worker': 'script' }
113 | })
114 | .then(response => {
115 | // Ensure service worker exists, and that we really are getting a JS file.
116 | const contentType = response.headers.get('content-type');
117 | if (
118 | response.status === 404 ||
119 | (contentType != null && contentType.indexOf('javascript') === -1)
120 | ) {
121 | // No service worker found. Probably a different app. Reload the page.
122 | navigator.serviceWorker.ready.then(registration => {
123 | registration.unregister().then(() => {
124 | window.location.reload();
125 | });
126 | });
127 | } else {
128 | // Service worker found. Proceed as normal.
129 | registerValidSW(swUrl, config);
130 | }
131 | })
132 | .catch(() => {
133 | console.log(
134 | 'No internet connection found. App is running in offline mode.'
135 | );
136 | });
137 | }
138 |
139 | export function unregister() {
140 | if ('serviceWorker' in navigator) {
141 | navigator.serviceWorker.ready
142 | .then(registration => {
143 | registration.unregister();
144 | })
145 | .catch(error => {
146 | console.error(error.message);
147 | });
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/packages/playground/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/packages/playground/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "react",
21 | "noFallthroughCasesInSwitch": true
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/packages/react-gsap/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | [
4 | "@babel/plugin-transform-typescript",
5 | {
6 | "allowDeclareFields": true
7 | }
8 | ]
9 | ]
10 | }
--------------------------------------------------------------------------------
/packages/react-gsap/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on: [push]
3 | jobs:
4 | build:
5 | runs-on: ubuntu-latest
6 |
7 | steps:
8 | - name: Begin CI...
9 | uses: actions/checkout@v2
10 |
11 | - name: Use Node 12
12 | uses: actions/setup-node@v1
13 | with:
14 | node-version: 12.x
15 |
16 | - name: Use cached node_modules
17 | uses: actions/cache@v1
18 | with:
19 | path: node_modules
20 | key: nodeModules-${{ hashFiles('**/yarn.lock') }}
21 | restore-keys: |
22 | nodeModules-
23 |
24 | - name: Install dependencies
25 | run: yarn install --frozen-lockfile
26 | env:
27 | CI: true
28 |
29 | - name: Lint
30 | run: yarn lint
31 | env:
32 | CI: true
33 |
34 | - name: Test
35 | run: yarn test --ci --coverage --maxWorkers=2
36 | env:
37 | CI: true
38 |
39 | - name: Build
40 | run: yarn build
41 | env:
42 | CI: true
43 |
--------------------------------------------------------------------------------
/packages/react-gsap/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | .cache
5 | dist
6 |
--------------------------------------------------------------------------------
/packages/react-gsap/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Jan Fischer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/packages/react-gsap/README.md:
--------------------------------------------------------------------------------
1 | # react-gsap
2 |
3 | > React components for GSAP
4 |
5 | [](https://www.npmjs.com/package/react-gsap)
6 | 
7 | 
8 |
9 | # Introduction
10 |
11 | `react-gsap` lets you use the GreenSock Animation Platform (GSAP) in React in a fully declarative way.
12 | It abstracts away the direct use of the GSAP [Tween](https://greensock.com/docs/v3/GSAP/Tween) and [Timeline](https://greensock.com/docs/v3/GSAP/Timeline) functions.
13 |
14 | If you need the full control it's possible by getting low level access to the underlying objects.
15 |
16 | In addition to that it ships some GSAP Plugins and useful helper components.
17 |
18 | From version 2 on it's build for GSAP 3 and only has `gsap` as a peer dependency. In this way you can update `gsap` separately from `react-gsap`.
19 |
20 | It's built with TypeScript and ships the types directly in the package.
21 |
22 | Documentation and examples are here: https://bitworking.github.io/react-gsap/
23 |
24 | ##### The examples on the documentation pages are all editable directly in the browser. So play with it!
25 |
26 | ## Installation
27 |
28 | ```bash
29 | npm install gsap react-gsap
30 | ```
31 |
32 | ## About GSAP
33 |
34 | GreenSock Animation Platform (GSAP) is a set of some JavaScript functions which let you tween a value/attribute/css property over time and insert these tweens into a timeline for more complex animations.
35 |
36 | `react-gsap` just adds some React wrapper components for these functions, so also read the official GreenSock documentation to know how to do things:
37 |
38 | [GreenSock Docs](https://greensock.com/docs/)
39 |
40 |
41 | ## License
42 |
43 | MIT © [bitworking](https://github.com/bitworking)
44 |
--------------------------------------------------------------------------------
/packages/react-gsap/example/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache
3 | dist
--------------------------------------------------------------------------------
/packages/react-gsap/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Playground
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/packages/react-gsap/example/index.tsx:
--------------------------------------------------------------------------------
1 | import 'react-app-polyfill/ie11';
2 | import * as React from 'react';
3 | import * as ReactDOM from 'react-dom';
4 | import { Thing } from '../.';
5 |
6 | const App = () => {
7 | return (
8 |
9 |
10 |
11 | );
12 | };
13 |
14 | ReactDOM.render( , document.getElementById('root'));
15 |
--------------------------------------------------------------------------------
/packages/react-gsap/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "start": "parcel index.html",
8 | "build": "parcel build index.html"
9 | },
10 | "dependencies": {
11 | "react-app-polyfill": "^1.0.0"
12 | },
13 | "alias": {
14 | "react": "../node_modules/react",
15 | "react-dom": "../node_modules/react-dom/profiling",
16 | "scheduler/tracing": "../node_modules/scheduler/tracing-profiling"
17 | },
18 | "devDependencies": {
19 | "@types/react": "^16.9.11",
20 | "@types/react-dom": "^16.8.4",
21 | "parcel": "^1.12.3",
22 | "typescript": "^3.4.5"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/react-gsap/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": false,
4 | "target": "es5",
5 | "module": "commonjs",
6 | "jsx": "react",
7 | "moduleResolution": "node",
8 | "noImplicitAny": false,
9 | "noUnusedLocals": false,
10 | "noUnusedParameters": false,
11 | "removeComments": true,
12 | "strictNullChecks": true,
13 | "preserveConstEnums": true,
14 | "sourceMap": true,
15 | "lib": ["es2015", "es2016", "dom"],
16 | "baseUrl": ".",
17 | "types": ["node"]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/react-gsap/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-gsap",
3 | "version": "3.3.0",
4 | "description": "React components for GSAP",
5 | "author": "bitworking",
6 | "license": "MIT",
7 | "repository": "bitworking/react-gsap",
8 | "main": "dist/index.js",
9 | "typings": "dist/index.d.ts",
10 | "module": "dist/react-gsap.esm.js",
11 | "files": [
12 | "dist",
13 | "src"
14 | ],
15 | "engines": {
16 | "node": ">=10"
17 | },
18 | "scripts": {
19 | "start": "tsdx watch",
20 | "build": "tsdx build --format cjs,esm,umd",
21 | "test": "tsdx test --passWithNoTests",
22 | "lint": "tsdx lint",
23 | "prepare": "yarn build",
24 | "typecheck": "tsc --project tsconfig.json -noEmit"
25 | },
26 | "peerDependencies": {
27 | "gsap": ">=3",
28 | "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0",
29 | "react-dom": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0"
30 | },
31 | "husky": {
32 | "hooks": {
33 | "pre-commit": "tsdx lint"
34 | }
35 | },
36 | "prettier": {
37 | "printWidth": 100,
38 | "tabWidth": 2,
39 | "trailingComma": "es5",
40 | "semi": true,
41 | "singleQuote": true,
42 | "endOfLine": "lf"
43 | },
44 | "devDependencies": {
45 | "@babel/plugin-transform-typescript": "^7.19.1",
46 | "@types/react": "^18.0.20",
47 | "@types/react-dom": "^18.0.6",
48 | "@types/react-is": "^17.0.3",
49 | "gsap": "^3.11.1",
50 | "husky": "^8.0.1",
51 | "react": "^18.2.0",
52 | "react-dom": "^18.2.0",
53 | "tsdx": "^0.14.1",
54 | "tslib": "^2.4.0",
55 | "typescript": "^4.8.3"
56 | },
57 | "dependencies": {
58 | "react-is": "^18.2.0"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/packages/react-gsap/src/Provider.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | type RegisteredPlugins = 'scrollTrigger';
4 | type Plugin = (targets: any) => any;
5 | type Plugins = { [key in RegisteredPlugins]: Plugin } | {};
6 |
7 | export type ContextProps = {
8 | registerConsumer: (consumer: any) => void;
9 | plugins?: Plugins;
10 | getPlugins: (plugins?: Plugins, targets?: any) => any;
11 | };
12 |
13 | export const Context = React.createContext({
14 | registerConsumer: () => {},
15 | getPlugins: () => {},
16 | plugins: {},
17 | });
18 |
19 | abstract class Provider extends React.Component {
20 | static contextType = Context;
21 | declare context: React.ContextType;
22 |
23 | consumers: any[];
24 | plugins?: Plugins;
25 |
26 | constructor(props: T) {
27 | super(props);
28 | this.consumers = [];
29 | this.plugins = {};
30 |
31 | this.registerConsumer = this.registerConsumer.bind(this);
32 | this.getContextValue = this.getContextValue.bind(this);
33 | this.getPlugin = this.getPlugin.bind(this);
34 | this.getPlugins = this.getPlugins.bind(this);
35 | this.renderWithProvider = this.renderWithProvider.bind(this);
36 | }
37 |
38 | registerConsumer(consumer: any) {
39 | this.consumers.push(consumer);
40 | }
41 |
42 | getContextValue(plugin: Plugins = {}) {
43 | return {
44 | registerConsumer: this.registerConsumer,
45 | // plugins: { ...this.context.plugins, ...plugin },
46 | plugins: plugin,
47 | getPlugins: this.getPlugins,
48 | };
49 | }
50 |
51 | getPlugin(props: any, targets: any) {
52 | return {};
53 | }
54 |
55 | getPlugins(plugins?: Plugins, targets?: any) {
56 | return Object.keys(plugins ?? {}).reduce((acc, plugin) => {
57 | if (Object.prototype.hasOwnProperty.call(plugins, plugin)) {
58 | // @ts-ignore
59 | return { ...acc, [plugin]: this.getPlugin(plugins[plugin], targets) };
60 | }
61 | return acc;
62 | }, {});
63 | }
64 |
65 | renderWithProvider(output: any, plugin?: Plugins) {
66 | return {output} ;
67 | }
68 | }
69 |
70 | export default Provider;
71 |
--------------------------------------------------------------------------------
/packages/react-gsap/src/Timeline.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment, ReactElement, ReactNode } from 'react';
2 | import { gsap } from 'gsap';
3 | import { isForwardRef, isFragment } from 'react-is';
4 | import { PlayState } from './types';
5 | import {
6 | getInitialPaused,
7 | getTargetRefProp,
8 | getTweenFunction,
9 | nullishCoalescing,
10 | setInitialPlayState,
11 | setPlayState,
12 | setProps,
13 | } from './helper';
14 | import Provider, { Context } from './Provider';
15 | import { TweenProps } from './Tween';
16 |
17 | type Label = {
18 | label: string;
19 | position: string | number;
20 | };
21 |
22 | export type Targets = Map;
23 | export type TargetsRef = {
24 | set: (key: string, target: any) => void;
25 | };
26 |
27 | export type Target = ReactElement | null;
28 |
29 | export type TimelineProps = {
30 | children: ReactNode;
31 | wrapper?: ReactElement;
32 | target?: Target;
33 | position?: string | number;
34 | labels?: Label[];
35 |
36 | duration?: number;
37 | progress?: number;
38 | totalProgress?: number;
39 | playState?: PlayState;
40 |
41 | [prop: string]: any;
42 | };
43 |
44 | class Timeline extends Provider {
45 | static displayName = 'Timeline';
46 |
47 | timeline: any;
48 | targets: Targets = new Map();
49 |
50 | static defaultProps = {
51 | playState: PlayState.play,
52 | };
53 |
54 | constructor(props: TimelineProps) {
55 | super(props);
56 |
57 | this.addTarget = this.addTarget.bind(this);
58 | this.setTarget = this.setTarget.bind(this);
59 | }
60 |
61 | componentDidMount() {
62 | this.createTimeline();
63 |
64 | // props at mount
65 | setProps(this.timeline, this.props);
66 | setInitialPlayState(this.timeline, this.props);
67 |
68 | this.context.registerConsumer(this);
69 | }
70 |
71 | componentWillUnmount() {
72 | this.timeline.kill();
73 | }
74 |
75 | getSnapshotBeforeUpdate() {
76 | this.targets = new Map();
77 | return null;
78 | }
79 |
80 | componentDidUpdate(prevProps: TimelineProps) {
81 | const { children, duration, progress, totalProgress, playState, target } = this.props;
82 |
83 | // if children change create a new timeline
84 | // TODO: replace easy length check with fast equal check
85 | // TODO: same for props.target?
86 | if (React.Children.count(prevProps.children) !== React.Children.count(children)) {
87 | this.createTimeline();
88 | }
89 |
90 | // execute function calls
91 | setProps(this.timeline, this.props, prevProps);
92 |
93 | // TODO: need rerender or something if target change?
94 | // if (target !== prevProps.target) {
95 | // this.forceUpdate();
96 | // }
97 |
98 | setPlayState(playState, prevProps.playState, this.timeline);
99 | }
100 |
101 | createTimeline() {
102 | const {
103 | children,
104 | target,
105 | duration,
106 | progress,
107 | totalProgress,
108 | playState,
109 | labels,
110 | position,
111 | ...vars
112 | } = this.props;
113 |
114 | if (this.timeline) {
115 | this.timeline.kill();
116 | }
117 |
118 | const plugins = this.context?.getPlugins(this.context?.plugins, this.targets) ?? {};
119 |
120 | // init timeline
121 | this.timeline = gsap.timeline({
122 | smoothChildTiming: true,
123 | paused: getInitialPaused(playState),
124 | ...vars,
125 | ...plugins,
126 | });
127 |
128 | if (labels) {
129 | labels.forEach(label => {
130 | this.timeline.addLabel(label.label, label.position);
131 | });
132 | }
133 |
134 | // add tweens or nested timelines to timeline
135 | this.consumers.forEach(consumer => {
136 | // Tween with no children -> control Timeline target
137 | if (consumer.tween && !consumer.props.children) {
138 | const { position, target, stagger, ...vars } = consumer.props as TweenProps;
139 |
140 | // get target if not nullish
141 | let targets = null;
142 | if (target !== null && typeof target !== 'undefined') {
143 | targets = this.targets.get(target);
144 | }
145 |
146 | // if no target found -> take all Timeline targets as target
147 | const tween = getTweenFunction(
148 | // @ts-ignore
149 | nullishCoalescing(targets, Array.from(this.targets.values())),
150 | {
151 | stagger,
152 | ...vars,
153 | }
154 | );
155 | this.timeline.add(tween, nullishCoalescing(position, '+=0'));
156 | consumer.setGSAP(tween);
157 | } else {
158 | const { position } = consumer.props;
159 | this.timeline.add(consumer.getGSAP(), nullishCoalescing(position, '+=0'));
160 | }
161 | });
162 | }
163 |
164 | getGSAP() {
165 | return this.timeline;
166 | }
167 |
168 | addTarget(target: any) {
169 | if (target !== null) {
170 | this.targets.set(this.targets.size, target);
171 | }
172 | }
173 |
174 | setTarget(key: string, target: any) {
175 | if (target !== null) {
176 | if (this.targets.has(key)) {
177 | const targets = this.targets.get(key);
178 | if (Array.isArray(targets)) {
179 | this.targets.set(key, [...targets, target]);
180 | } else {
181 | this.targets.set(key, [targets, target]);
182 | }
183 | } else {
184 | this.targets.set(key, target);
185 | }
186 | }
187 | }
188 |
189 | setTargets(targets: Targets) {
190 | this.targets = targets;
191 | }
192 |
193 | getTargets() {
194 | return this.targets;
195 | }
196 |
197 | cloneElement(child: any) {
198 | // @ts-ignore
199 | return React.cloneElement(child, getTargetRefProp(child, this.setTarget, this.addTarget));
200 | }
201 |
202 | renderTarget(target?: Target): ReactNode {
203 | if (!target) {
204 | return null;
205 | }
206 |
207 | // if is forwardRef clone and pass targets as ref
208 | if (isForwardRef(target)) {
209 | return this.cloneElement(target);
210 | }
211 |
212 | // else iterate the first level of children and set targets
213 | return (
214 |
215 | {/* First render the target */}
216 | {React.Children.map(target, child => {
217 | if (isFragment(child)) {
218 | return React.Children.map(child.props.children, fragmentChild => {
219 | return this.cloneElement(fragmentChild);
220 | });
221 | }
222 | return this.cloneElement(child);
223 | })}
224 |
225 | );
226 | }
227 |
228 | render() {
229 | let { target, children, wrapper } = this.props;
230 |
231 | const renderedTarget = this.renderTarget(target);
232 |
233 | let output = (
234 |
235 | {renderedTarget}
236 | {children}
237 |
238 | );
239 |
240 | if (wrapper) {
241 | output = React.cloneElement(wrapper, [], output);
242 | }
243 |
244 | return this.renderWithProvider(output);
245 | }
246 | }
247 |
248 | export default Timeline;
249 |
--------------------------------------------------------------------------------
/packages/react-gsap/src/Tween.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment, ReactElement } from 'react';
2 | import { gsap } from 'gsap';
3 | import { PlayState } from './types';
4 | import {
5 | getTweenFunction,
6 | setPlayState,
7 | isEqual,
8 | getRefProp,
9 | setProps,
10 | setInitialPlayState,
11 | } from './helper';
12 | import { Context } from './Provider';
13 |
14 | import SvgDrawPlugin from './plugins/PlugInSvgDraw';
15 | import CountPlugin from './plugins/PlugInCount';
16 |
17 | // @ts-ignore
18 | gsap.registerPlugin(SvgDrawPlugin);
19 |
20 | // @ts-ignore
21 | gsap.registerPlugin(CountPlugin);
22 |
23 | type StaggerFunction = (index: number, target: any, list: any) => number;
24 | type StaggerFromValues = 'start' | 'center' | 'edges' | 'random' | 'end';
25 | type EaseFunction = (value: number) => number;
26 |
27 | export type Stagger =
28 | | {
29 | amount?: number;
30 | each?: number;
31 | from?: StaggerFromValues | number | [number, number];
32 | grid?: [number, number] | 'auto';
33 | axis?: 'x' | 'y';
34 | ease?: string | EaseFunction;
35 | repeat?: number;
36 | yoyo?: boolean;
37 | [prop: string]: any;
38 | }
39 | | number
40 | | StaggerFunction;
41 |
42 | export type TweenProps = {
43 | /** One or multiple "refable" components */
44 | children?: React.ReactNode;
45 | wrapper?: React.ReactElement;
46 | target?: number | string;
47 | position?: string | number;
48 |
49 | from?: any;
50 | to?: any;
51 | stagger?: Stagger;
52 |
53 | duration?: number;
54 | progress?: number;
55 | totalProgress?: number;
56 | playState?: PlayState;
57 |
58 | disabled?: boolean;
59 | onlyInvalidateTo?: boolean;
60 |
61 | [prop: string]: any;
62 | };
63 |
64 | class Tween extends React.Component {
65 | static displayName = 'Tween';
66 | static contextType = Context;
67 | declare context: React.ContextType;
68 |
69 | tween: any;
70 | targets: any[] = [];
71 |
72 | constructor(props: TweenProps) {
73 | super(props);
74 |
75 | this.addTarget = this.addTarget.bind(this);
76 | }
77 |
78 | componentDidMount() {
79 | this.createTween();
80 |
81 | // props at mount
82 | setProps(this.tween, this.props);
83 | setInitialPlayState(this.tween, this.props);
84 |
85 | this.context.registerConsumer(this);
86 | }
87 |
88 | componentWillUnmount() {
89 | if (this.tween) {
90 | this.tween.kill();
91 | }
92 | }
93 |
94 | getSnapshotBeforeUpdate() {
95 | this.targets = [];
96 | return null;
97 | }
98 |
99 | componentDidUpdate(prevProps: TweenProps) {
100 | const {
101 | children,
102 | wrapper,
103 |
104 | duration,
105 | from,
106 | to,
107 | stagger,
108 |
109 | progress,
110 | totalProgress,
111 | playState,
112 | disabled,
113 | onlyInvalidateTo,
114 |
115 | onCompleteAll,
116 | onCompleteAllParams,
117 | onCompleteAllScope,
118 | onStartAll,
119 |
120 | position,
121 | target,
122 |
123 | ...vars
124 | } = this.props;
125 |
126 | // if children change create a new tween
127 | // TODO: replace easy length check with fast equal check
128 | if (React.Children.count(prevProps.children) !== React.Children.count(children)) {
129 | this.createTween();
130 | }
131 |
132 | if (disabled) {
133 | return;
134 | }
135 |
136 | // execute function calls
137 | setProps(this.tween, this.props, prevProps);
138 |
139 | // if "to" props are changed: reinit and restart tween
140 | if (!isEqual(to, prevProps.to)) {
141 | // is Tween
142 | if (!this.tween.getChildren) {
143 | this.tween.vars = { ...to, ...vars };
144 |
145 | if (onlyInvalidateTo) {
146 | var progressTmp = this.tween.progress();
147 | this.tween
148 | .progress(0)
149 | .invalidate()
150 | .progress(progressTmp);
151 | } else {
152 | this.tween.invalidate();
153 | }
154 | }
155 | // is Timeline
156 | // TODO: not yet ready
157 | else {
158 | let delay = 0;
159 | this.tween.getChildren(false, true, false).forEach((tween: any) => {
160 | tween.vars = { ...to, ...vars, ...{ delay } };
161 | tween.invalidate();
162 | // delay += stagger || 0;
163 | });
164 | }
165 |
166 | if (!this.tween.paused()) {
167 | this.tween.restart();
168 | }
169 | }
170 |
171 | setPlayState(playState, prevProps.playState, this.tween);
172 | }
173 |
174 | createTween() {
175 | if (this.tween) {
176 | this.tween.kill();
177 | }
178 |
179 | if (this.props.children) {
180 | this.tween = getTweenFunction(this.targets, this.props, this.context);
181 | } else {
182 | // why this is needed?
183 | this.tween = () => {};
184 | }
185 | }
186 |
187 | getGSAP() {
188 | return this.tween;
189 | }
190 |
191 | setGSAP(tween: any) {
192 | this.tween = tween;
193 | }
194 |
195 | addTarget(target: any) {
196 | // target is null at unmount
197 | if (target !== null) {
198 | this.targets.push(target);
199 | }
200 | }
201 |
202 | getTargets() {
203 | return this.targets;
204 | }
205 |
206 | render() {
207 | let { children, wrapper } = this.props;
208 |
209 | const output = (
210 |
211 | {React.Children.map(children, child => {
212 | return React.cloneElement(child as ReactElement, getRefProp(child, this.addTarget));
213 | })}
214 |
215 | );
216 |
217 | if (wrapper) {
218 | return React.cloneElement(wrapper, [], output);
219 | }
220 |
221 | return output;
222 | }
223 | }
224 |
225 | export default Tween;
226 |
--------------------------------------------------------------------------------
/packages/react-gsap/src/helper.ts:
--------------------------------------------------------------------------------
1 | import { gsap } from 'gsap';
2 | import React from 'react';
3 | import { PlayState } from './types';
4 | import { TimelineProps } from 'Timeline';
5 | import { TweenProps } from 'Tween';
6 | import { ContextProps } from 'Provider';
7 |
8 | if (!String.prototype.startsWith) {
9 | String.prototype.startsWith = function(searchString, position) {
10 | position = position || 0;
11 | return this.indexOf(searchString, position) === position;
12 | };
13 | }
14 |
15 | const setPlayState = (
16 | playState?: PlayState,
17 | prevPlayState?: PlayState | null,
18 | tween: any = null
19 | ) => {
20 | if (tween && playState && playState !== prevPlayState) {
21 | if (playState === PlayState.play) {
22 | tween.play();
23 | } else if (playState === PlayState.restart) {
24 | tween.restart(true);
25 | } else if (playState === PlayState.reverse) {
26 | tween.reverse();
27 | } else if (playState === PlayState.restartReverse) {
28 | tween.reverse(0);
29 | } else if (playState === PlayState.stop) {
30 | tween.pause(0);
31 | } else if (playState === PlayState.stopEnd) {
32 | tween.reverse(0);
33 | tween.pause();
34 | } else if (playState === PlayState.pause) {
35 | tween.pause();
36 | } else if (playState === PlayState.resume) {
37 | tween.resume();
38 | }
39 | }
40 | };
41 |
42 | const setInitialPlayState = (tweenOrTimeline: any, props: TimelineProps | TweenProps) => {
43 | const { playState } = props;
44 | if (playState) {
45 | setPlayState(playState, PlayState.play, tweenOrTimeline);
46 | }
47 | };
48 |
49 | const getInitialPaused = (playState?: PlayState) => {
50 | return (
51 | playState &&
52 | (playState === PlayState.stop ||
53 | playState === PlayState.stopEnd ||
54 | playState === PlayState.pause)
55 | );
56 | };
57 |
58 | const getTweenFunction = (
59 | targets: any,
60 | props: TweenProps | TimelineProps,
61 | context?: ContextProps
62 | ): gsap.core.Tween | gsap.core.Timeline => {
63 | const {
64 | children,
65 | wrapper,
66 |
67 | duration = 1,
68 | from,
69 | to,
70 |
71 | stagger,
72 |
73 | progress,
74 | totalProgress,
75 | playState,
76 | disabled,
77 | onlyInvalidateTo,
78 |
79 | onCompleteAll,
80 | onCompleteAllParams,
81 | onCompleteAllScope,
82 | onStartAll,
83 |
84 | position,
85 | target,
86 |
87 | ...vars
88 | } = props;
89 |
90 | let tweenFunction: gsap.core.Tween | gsap.core.Timeline;
91 | const paused = getInitialPaused(playState);
92 | const plugins = context?.getPlugins(context?.plugins, targets) ?? {};
93 |
94 | if (from && to) {
95 | // special props like paused always go in the toVars parameter
96 | tweenFunction = gsap.fromTo(targets, from, {
97 | stagger,
98 | duration,
99 | paused,
100 | ...to,
101 | ...vars,
102 | ...plugins,
103 | });
104 | } else if (to) {
105 | tweenFunction = gsap.to(targets, { stagger, duration, paused, ...to, ...vars, ...plugins });
106 | } else {
107 | tweenFunction = gsap.from(targets, { stagger, duration, paused, ...from, ...vars, ...plugins });
108 | }
109 |
110 | // if multiple tweens (stagger), wrap them in a timeline
111 | // TODO: if it's already an timeline add event handlers
112 | if (Array.isArray(tweenFunction)) {
113 | tweenFunction.forEach(t => {
114 | t.paused(false);
115 | });
116 | tweenFunction = gsap.timeline({
117 | ...vars,
118 | tweens: tweenFunction,
119 | smoothChildTiming: true,
120 | onComplete: onCompleteAll,
121 | onCompleteParams: onCompleteAllParams,
122 | onCompleteScope: onCompleteAllScope,
123 | onStart: onStartAll,
124 | });
125 | }
126 |
127 | return tweenFunction;
128 | };
129 |
130 | const callTweenFunction = (
131 | tweenFunction: any,
132 | functionName: string,
133 | params: Array | undefined = undefined,
134 | returnFunction: string | undefined = undefined
135 | ): void => {
136 | if (Array.isArray(tweenFunction)) {
137 | tweenFunction.forEach(tween => {
138 | if (!params && returnFunction) {
139 | params = [tween[returnFunction].apply(tween)];
140 | }
141 | tween[functionName].apply(tween, params);
142 | });
143 | } else {
144 | if (!params && returnFunction) {
145 | params = [tweenFunction[returnFunction].apply(tweenFunction)];
146 | }
147 | tweenFunction[functionName].apply(tweenFunction, params);
148 | }
149 | };
150 |
151 | const isEqual = (obj1: any, obj2: any) => {
152 | // very easy equal check
153 | // attention: if the order of properties are different it returns false
154 | return JSON.stringify(obj1) === JSON.stringify(obj2);
155 | };
156 |
157 | const refOrInnerRef = (child: any) => {
158 | if (child.type.$$typeof && child.type.$$typeof.toString() === 'Symbol(react.forward_ref)') {
159 | return 'ref';
160 | }
161 |
162 | // styled-components < 4
163 | if (child.type.styledComponentId) {
164 | return 'innerRef';
165 | }
166 |
167 | return 'ref';
168 | };
169 |
170 | function isElement(element: any) {
171 | return React.isValidElement(element);
172 | }
173 |
174 | function isDOMTypeElement(element: any) {
175 | return isElement(element) && typeof element.type === 'string';
176 | }
177 |
178 | // https://stackoverflow.com/a/39165137
179 | function getReactNode(dom: any, traverseUp = 0) {
180 | const key = Object.keys(dom ?? {}).find(
181 | key => key.startsWith('__reactInternalInstance$') || key.startsWith('__reactFiber$')
182 | );
183 |
184 | const domFiber = key && dom[key];
185 | if (!domFiber) return null;
186 |
187 | // react <16
188 | if (domFiber._currentElement) {
189 | let compFiber = domFiber._currentElement._owner;
190 | for (let i = 0; i < traverseUp; i++) {
191 | compFiber = compFiber._currentElement._owner;
192 | }
193 | return compFiber._instance;
194 | }
195 |
196 | // react 16+
197 | if (domFiber.stateNode) {
198 | return domFiber.stateNode;
199 | }
200 |
201 | const getCompFiber = (fiber: any) => {
202 | //return fiber._debugOwner; // this also works, but is __DEV__ only
203 | let parentFiber = fiber.return;
204 | while (typeof parentFiber.type == 'string') {
205 | parentFiber = parentFiber.return;
206 | }
207 | return parentFiber;
208 | };
209 | let compFiber = getCompFiber(domFiber);
210 | for (let i = 0; i < traverseUp; i++) {
211 | compFiber = getCompFiber(compFiber);
212 | }
213 | return compFiber.stateNode;
214 | }
215 |
216 | const getRefProp = (child: any, addTarget: (target: any) => void) => {
217 | // has to be tested if it works, which lib does still use innerRef?
218 | if (child.props.innerRef) {
219 | return {
220 | innerRef: (target: any) => {
221 | addTarget(target);
222 | const { innerRef } = child.props;
223 | if (typeof innerRef === 'function') innerRef(target);
224 | else if (innerRef) innerRef.current = target;
225 | },
226 | };
227 | }
228 |
229 | return {
230 | ref: (target: any) => {
231 | addTarget(target);
232 | const { ref } = child;
233 | if (typeof ref === 'function') ref(target);
234 | else if (ref) ref.current = target;
235 | },
236 | };
237 | };
238 |
239 | const setOrAddTarget = (
240 | target: any,
241 | setTarget: (key: string, target: any) => void,
242 | addTarget: (target: any) => void
243 | ) => {
244 | const reactNode = getReactNode(target);
245 |
246 | if (reactNode) {
247 | addTarget(reactNode);
248 | } else if (target) {
249 | Object.keys(target).forEach(key => {
250 | const elementRef = target[key];
251 | if (typeof elementRef === 'object' && elementRef.current) {
252 | if (Array.isArray(elementRef.current)) {
253 | elementRef.current.forEach((singleRef: React.RefObject) => {
254 | setTarget(key, singleRef);
255 | });
256 | } else {
257 | setTarget(key, elementRef.current);
258 | }
259 | }
260 | });
261 | }
262 | };
263 |
264 | const getTargetRefProp = (
265 | child: any,
266 | setTarget: (key: string, target: any) => void,
267 | addTarget: (target: any) => void
268 | ) => {
269 | // has to be tested if it works, which lib does still use innerRef?
270 | if (child.props.innerRef) {
271 | return {
272 | innerRef: (target: any) => {
273 | setOrAddTarget(target, setTarget, addTarget);
274 | // merge refs
275 | const { innerRef } = child.props;
276 | if (typeof innerRef === 'function') innerRef(target);
277 | else if (innerRef) innerRef.current = target;
278 | },
279 | };
280 | }
281 |
282 | return {
283 | ref: (target: any) => {
284 | setOrAddTarget(target, setTarget, addTarget);
285 | // merge refs
286 | const { ref } = child;
287 | if (typeof ref === 'function') ref(target);
288 | else if (ref) ref.current = target;
289 | },
290 | };
291 | };
292 |
293 | const nullishCoalescing = (value: T, ifNullish: R): T | R => {
294 | if (value === null || typeof value === 'undefined') {
295 | return ifNullish;
296 | }
297 | return value;
298 | };
299 |
300 | const setProps = (
301 | tweenOrTimeline: any,
302 | props: TimelineProps | TweenProps,
303 | prevProps?: TimelineProps | TweenProps
304 | ) => {
305 | if (props.progress !== undefined && props.progress !== prevProps?.progress) {
306 | tweenOrTimeline.progress(props.progress);
307 | }
308 | if (props.totalProgress !== undefined && props.totalProgress !== prevProps?.totalProgress) {
309 | tweenOrTimeline.totalProgress(props.totalProgress);
310 | }
311 | if (
312 | tweenOrTimeline.duration !== undefined &&
313 | props.duration &&
314 | props.duration !== prevProps?.duration
315 | ) {
316 | tweenOrTimeline.duration(props.duration);
317 | }
318 | };
319 |
320 | export {
321 | getTweenFunction,
322 | callTweenFunction,
323 | setPlayState,
324 | isEqual,
325 | refOrInnerRef,
326 | getRefProp,
327 | getTargetRefProp,
328 | nullishCoalescing,
329 | setProps,
330 | setInitialPlayState,
331 | getInitialPaused,
332 | };
333 |
--------------------------------------------------------------------------------
/packages/react-gsap/src/index.tsx:
--------------------------------------------------------------------------------
1 | export { default as Tween } from './Tween';
2 | export { default as Timeline } from './Timeline';
3 | export { default as Reveal } from './tools/Reveal';
4 | // export { default as Scroller } from './tools/Scroller';
5 | export { default as ScrollTrigger } from './tools/ScrollTrigger';
6 | export { SplitWords, SplitChars, SplitLetters } from './tools/SplitText';
7 | export { default as Controls } from './tools/Controls';
8 | export { PlayState } from './types';
9 |
--------------------------------------------------------------------------------
/packages/react-gsap/src/plugins/PlugInCount.ts:
--------------------------------------------------------------------------------
1 | import { nullishCoalescing } from '../helper';
2 |
3 | let gsap: any;
4 | let _interpolate: any;
5 | let _format: any;
6 | const _getGSAP = () =>
7 | gsap || (typeof window !== 'undefined' && (gsap = window.gsap) && gsap.registerPlugin && gsap);
8 |
9 | type Prop =
10 | | {
11 | value: string | number;
12 | format: () => (value: string | number) => number | string;
13 | }
14 | | number
15 | | string;
16 |
17 | export const CountPlugin = {
18 | version: '1.0.0',
19 | name: 'count',
20 | register(core: any, Plugin: any, propTween: any) {
21 | gsap = core;
22 | _interpolate = gsap.utils.interpolate;
23 | _format = (value: string | number) => parseInt(value.toString(), 10);
24 | },
25 | init(target: any, value: Prop, _tween: any, index: number, targets: any) {
26 | let inputValue = value;
27 | let format = _format;
28 | if (typeof value === 'object') {
29 | inputValue = nullishCoalescing(value.value, 0);
30 | if (value.format) {
31 | format = value.format;
32 | }
33 | }
34 |
35 | const initialCount = parseFloat(target.innerText);
36 |
37 | let data = this;
38 | data.target = target;
39 | data.count = _interpolate(initialCount, parseFloat(inputValue.toString()));
40 | data.format = format;
41 | },
42 | render(progress: number, data: any) {
43 | data.target.innerText = data.format(data.count(progress));
44 | },
45 | };
46 |
47 | _getGSAP() && gsap.registerPlugin(CountPlugin);
48 |
49 | export { CountPlugin as default };
50 |
--------------------------------------------------------------------------------
/packages/react-gsap/src/plugins/PlugInSvgDraw.ts:
--------------------------------------------------------------------------------
1 | import { nullishCoalescing } from '../helper';
2 |
3 | let gsap: any;
4 | let _interpolate: any;
5 | let _getProp: any;
6 | const _getGSAP = () =>
7 | gsap || (typeof window !== 'undefined' && (gsap = window.gsap) && gsap.registerPlugin && gsap);
8 |
9 | type Point = {
10 | x: number;
11 | y: number;
12 | };
13 |
14 | function getDistance(p1: DOMPoint | Point, p2: DOMPoint | Point) {
15 | return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
16 | }
17 |
18 | function getCircleLength(el: SVGCircleElement) {
19 | return 2 * Math.PI * parseFloat(nullishCoalescing(el.getAttribute('r'), '1') || '0');
20 | }
21 |
22 | function getRectLength(el: SVGRectElement) {
23 | return (
24 | parseFloat(nullishCoalescing(el.getAttribute('width'), '1') || '0') * 2 +
25 | parseFloat(nullishCoalescing(el.getAttribute('height'), '1') || '0') * 2
26 | );
27 | }
28 |
29 | function getLineLength(el: SVGLineElement) {
30 | return getDistance(
31 | {
32 | x: parseFloat(nullishCoalescing(el.getAttribute('x1'), '1') || '0'),
33 | y: parseFloat(nullishCoalescing(el.getAttribute('y1'), '1') || '0'),
34 | },
35 | {
36 | x: parseFloat(nullishCoalescing(el.getAttribute('x2'), '1') || '0'),
37 | y: parseFloat(nullishCoalescing(el.getAttribute('y2'), '1') || '0'),
38 | }
39 | );
40 | }
41 |
42 | function getPolylineLength(el: SVGPolylineElement) {
43 | const points = el.points;
44 | let totalLength = 0;
45 | let previousPos: DOMPoint | undefined = undefined;
46 | for (let i = 0; i < points.numberOfItems; i++) {
47 | const currentPos = points.getItem(i);
48 | if (previousPos) totalLength += getDistance(previousPos, currentPos);
49 | previousPos = currentPos;
50 | }
51 | return totalLength;
52 | }
53 |
54 | function getPolygonLength(el: SVGPolylineElement) {
55 | const points = el.points;
56 | return (
57 | getPolylineLength(el) + getDistance(points.getItem(points.numberOfItems - 1), points.getItem(0))
58 | );
59 | }
60 |
61 | // if path is splitted into multiple move commands then return longest path
62 | function getPathLength(el: SVGPathElement) {
63 | if (!el.hasAttribute('d')) {
64 | return el.getTotalLength();
65 | }
66 | const d = el.getAttribute('d');
67 | const pathString = d ? d.replace(/m/gi, 'M') : null;
68 |
69 | if (!pathString) {
70 | return el.getTotalLength();
71 | }
72 |
73 | const paths = pathString
74 | .split('M')
75 | .filter(path => path !== '')
76 | .map(path => `M${path}`);
77 |
78 | if (paths.length === 1) {
79 | return el.getTotalLength();
80 | }
81 |
82 | let maxLength = 0;
83 |
84 | paths.forEach(path => {
85 | const pathElement = document.createElementNS('http://www.w3.org/2000/svg', 'path');
86 | pathElement.setAttribute('d', path);
87 | maxLength = Math.max(maxLength, pathElement.getTotalLength());
88 | });
89 |
90 | return maxLength;
91 | }
92 |
93 | function getTotalLength(el: any) {
94 | if (el.getTotalLength) {
95 | return getPathLength(el);
96 | }
97 | switch (el.tagName.toLowerCase()) {
98 | case 'circle':
99 | return getCircleLength(el);
100 | case 'rect':
101 | return getRectLength(el);
102 | case 'line':
103 | return getLineLength(el);
104 | case 'polyline':
105 | return getPolylineLength(el);
106 | case 'polygon':
107 | return getPolygonLength(el);
108 | default:
109 | return 0;
110 | }
111 | }
112 |
113 | export const SvgDrawPlugin = {
114 | version: '2.0.0',
115 | name: 'svgDraw',
116 | register(core: any, Plugin: any, propTween: any) {
117 | gsap = core;
118 | _interpolate = gsap.utils.interpolate;
119 | _getProp = gsap.getProperty;
120 | },
121 | init(target: any, value: number, _tween: any, index: number, targets: any) {
122 | const length = getTotalLength(target);
123 |
124 | let lengthParam = value;
125 | let offsetParam = 0;
126 |
127 | if (Array.isArray(value)) {
128 | lengthParam = value[0];
129 | if (value.length >= 2) {
130 | offsetParam = value[1] * -1;
131 | }
132 | }
133 |
134 | let data = this;
135 | data.target = target;
136 | data.strokeDashoffset = _interpolate(
137 | _getProp(target, 'stroke-dashoffset'),
138 | length * offsetParam
139 | );
140 | data.strokeDasharray = _interpolate(_getProp(target, 'stroke-dasharray'), [
141 | lengthParam * length,
142 | length,
143 | ]);
144 | },
145 | render(progress: number, data: any) {
146 | data.target.setAttribute('stroke-dashoffset', data.strokeDashoffset(progress));
147 | data.target.setAttribute('stroke-dasharray', data.strokeDasharray(progress));
148 | },
149 | };
150 |
151 | _getGSAP() && gsap.registerPlugin(SvgDrawPlugin);
152 |
153 | export { SvgDrawPlugin as default };
154 |
--------------------------------------------------------------------------------
/packages/react-gsap/src/tools/Controls.tsx:
--------------------------------------------------------------------------------
1 | import React, {ReactNode} from 'react';
2 | import { PlayState } from './../types';
3 | import { setPlayState } from './../helper';
4 | import Provider from '../Provider';
5 |
6 | type ControlsProps = {
7 | playState?: PlayState;
8 | children: ReactNode;
9 | };
10 |
11 | type ControlsState = {
12 | totalProgress: number;
13 | playState?: PlayState;
14 | prevPlayState?: PlayState;
15 | };
16 |
17 | class Controls extends Provider {
18 | gsap: any;
19 | slider: any;
20 | sliderTouched: boolean = false;
21 |
22 | state = {
23 | totalProgress: 0,
24 | playState: undefined,
25 | prevPlayState: undefined,
26 | };
27 |
28 | containerStyle = {
29 | backgroundColor: '#f0f0f0',
30 | padding: '10px 10px 0 10px',
31 | marginTop: '10px',
32 | position: 'relative' as 'relative',
33 | zIndex: 2,
34 | fontFamily: 'verdana, sans-serif',
35 | fontSize: '16px',
36 | border: '1px solid #ccc',
37 | };
38 |
39 | buttonContainerStyle = {
40 | margin: '0',
41 | display: 'flex',
42 | flexWrap: 'wrap' as 'wrap',
43 | justifyContent: 'space-between',
44 | };
45 |
46 | buttonStyle = {
47 | border: '1px solid #999',
48 | backgroundColor: '#f0f0f0',
49 | padding: '5px',
50 | margin: '10px 10px 10px 0',
51 | cursor: 'pointer',
52 | };
53 |
54 | sliderStyle = {
55 | margin: '0',
56 | width: '100%',
57 | };
58 |
59 | playStateStyle = {
60 | color: '#999',
61 | margin: '10px 0',
62 | fontSize: '14px',
63 | };
64 |
65 | componentDidMount() {
66 | if (this.consumers.length) {
67 | this.gsap = this.consumers[0];
68 |
69 | const gsap = this.gsap.getGSAP();
70 |
71 | if (gsap) {
72 | gsap.eventCallback('onUpdate', this.onUpdate);
73 |
74 | if (this.props.playState) {
75 | this.setPlayState(this.props.playState);
76 | } else {
77 | // get child initial state
78 | if (gsap.paused()) {
79 | this.setPlayState(PlayState.pause);
80 | } else if (gsap.reversed()) {
81 | this.setPlayState(PlayState.reverse);
82 | } else {
83 | this.setPlayState(PlayState.play);
84 | }
85 | }
86 |
87 | const totalProgress = gsap.totalProgress();
88 | this.slider.value = totalProgress * 100;
89 | }
90 | }
91 | }
92 |
93 | componentDidUpdate() {
94 | this.onUpdate();
95 | }
96 |
97 | onUpdate = () => {
98 | if (this.gsap && this.slider && !this.sliderTouched) {
99 | const totalProgress = this.gsap.getGSAP().totalProgress();
100 | this.slider.value = totalProgress * 100;
101 | }
102 | };
103 |
104 | onChange = (event: any) => {
105 | if (this.gsap && this.gsap.getGSAP()) {
106 | this.gsap.getGSAP().totalProgress(event.target.value / 100);
107 | }
108 | };
109 |
110 | setPlayState = (state: PlayState) => {
111 | this.setState(prevState => {
112 | return {
113 | playState: state,
114 | prevPlayState: prevState.playState,
115 | };
116 | });
117 | };
118 |
119 | getControls = (_totalProgress: any, playState: PlayState | undefined) => (
120 |
121 |
(this.slider = el)}
123 | type="range"
124 | style={this.sliderStyle}
125 | step="0.001"
126 | onChange={e => this.onChange(e)}
127 | onMouseDown={() => (this.sliderTouched = true)}
128 | onMouseUp={() => (this.sliderTouched = false)}
129 | />
130 |
131 |
132 | this.setPlayState(PlayState.play)}
136 | >
137 | Play
138 |
139 | this.setPlayState(PlayState.reverse)}
143 | >
144 | Reverse
145 |
146 | this.setPlayState(PlayState.pause)}
150 | >
151 | Pause
152 |
153 | this.setPlayState(PlayState.stop)}
157 | >
158 | Stop
159 |
160 |
161 |
{playState}
162 |
163 |
164 | );
165 |
166 | render() {
167 | const { children } = this.props;
168 | const { totalProgress, playState, prevPlayState } = this.state;
169 |
170 | if (this.gsap) {
171 | setPlayState(playState, prevPlayState, this.gsap.getGSAP());
172 | }
173 |
174 | return this.renderWithProvider(
175 |
176 | {children}
177 | {this.getControls(totalProgress, playState)}
178 |
179 | );
180 | }
181 | }
182 |
183 | export default Controls;
184 |
--------------------------------------------------------------------------------
/packages/react-gsap/src/tools/Reveal.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { gsap } from 'gsap';
3 | import { nullishCoalescing } from '../helper';
4 | import Provider from '../Provider';
5 |
6 | export type RevealProps = {
7 | children: React.ReactNode;
8 | trigger: React.ReactElement | null;
9 | repeat: boolean;
10 | root: Element | null;
11 | rootMargin: string;
12 | threshold: number;
13 | };
14 |
15 | enum EntryState {
16 | unknown,
17 | entered,
18 | exited,
19 | }
20 |
21 | class Reveal extends Provider {
22 | static displayName = 'Reveal';
23 |
24 | static defaultProps = {
25 | trigger: null,
26 | repeat: false,
27 | root: null,
28 | rootMargin: '0px',
29 | threshold: 0.66,
30 | };
31 |
32 | timeline: any;
33 | triggerRef: HTMLElement | null = null;
34 | observer: IntersectionObserver | null = null;
35 |
36 | init() {
37 | this.createTimeline();
38 | this.createIntersectionObserver();
39 | }
40 |
41 | kill() {
42 | this.killTimeline();
43 | this.killIntersectionObserver();
44 | }
45 |
46 | componentDidMount() {
47 | this.init();
48 | }
49 |
50 | componentWillUnmount() {
51 | this.kill();
52 | }
53 |
54 | componentDidUpdate(prevProps: RevealProps) {
55 | const { children, trigger } = this.props;
56 |
57 | // if children change create a new timeline
58 | // TODO: replace easy length check with fast equal check
59 | // TODO: same for props.target?
60 | if (React.Children.count(prevProps.children) !== React.Children.count(children)) {
61 | this.init();
62 | }
63 |
64 | if (prevProps.trigger !== trigger) {
65 | this.init();
66 | }
67 | }
68 |
69 | createTimeline() {
70 | this.killTimeline();
71 |
72 | // init timeline
73 | this.timeline = gsap.timeline({
74 | smoothChildTiming: true,
75 | paused: true,
76 | });
77 |
78 | // add consumers
79 | this.consumers.forEach(consumer => {
80 | const { position } = consumer.props;
81 | this.timeline.add(consumer.getGSAP().play(), nullishCoalescing(position, 0));
82 | });
83 | }
84 |
85 | killTimeline() {
86 | if (this.timeline) {
87 | this.timeline.kill();
88 | }
89 | }
90 |
91 | createIntersectionObserver() {
92 | let { root, rootMargin, threshold } = this.props;
93 |
94 | const options = {
95 | root,
96 | rootMargin,
97 | threshold: [0, threshold],
98 | };
99 |
100 | this.observer = new IntersectionObserver(this.intersectionObserverCallback, options);
101 |
102 | // It would be better if we wouldn't need an extra wrapper.
103 | // But it can be problematic for example with a fadeInLeft animation
104 | // were the element is out of the viewport in the initial state.
105 | // In this case there wouldn't be an intersection..
106 | if (!this.triggerRef) {
107 | this.consumers.forEach(consumer => {
108 | consumer.getTargets().forEach((target: any) => {
109 | this.observer && this.observer.observe(target);
110 | });
111 | });
112 | } else {
113 | this.observer && this.observer.observe(this.triggerRef);
114 | }
115 | }
116 |
117 | killIntersectionObserver() {
118 | this.unobserveAll();
119 | this.observer = null;
120 | }
121 |
122 | unobserveAll() {
123 | if (this.observer) {
124 | if (!this.triggerRef) {
125 | this.consumers.forEach(consumer => {
126 | consumer.getTargets().forEach((target: any) => {
127 | this.observer && this.observer.unobserve(target);
128 | });
129 | });
130 | } else {
131 | this.observer && this.observer.unobserve(this.triggerRef);
132 | }
133 | }
134 | }
135 |
136 | intersectionObserverCallback = (entries: any) => {
137 | let { repeat, threshold } = this.props;
138 | let state: EntryState = EntryState.unknown;
139 |
140 | for (const entry of entries) {
141 | if (entry.isIntersecting && entry.intersectionRatio >= threshold) {
142 | this.timeline.play();
143 | state = EntryState.entered;
144 | break;
145 | } else if (!entry.isIntersecting) {
146 | state = EntryState.exited;
147 | break;
148 | }
149 | }
150 |
151 | if (!repeat && state === EntryState.entered) {
152 | this.killIntersectionObserver();
153 | } else if (repeat && state === EntryState.exited) {
154 | this.timeline.pause(0);
155 | }
156 | };
157 |
158 | getGSAP() {
159 | return this.timeline;
160 | }
161 |
162 | render() {
163 | let { children, trigger } = this.props;
164 |
165 | let output = trigger ? (
166 | (this.triggerRef = trigger)}>
167 | {children}
168 |
169 | ) : (
170 | children
171 | );
172 |
173 | return this.renderWithProvider(output);
174 | }
175 | }
176 |
177 | export default Reveal;
178 |
--------------------------------------------------------------------------------
/packages/react-gsap/src/tools/ScrollTrigger.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { gsap } from 'gsap';
3 | import { ScrollTrigger as ScrollTriggerPlugin } from 'gsap/dist/ScrollTrigger';
4 | import Provider, { Context } from '../Provider';
5 |
6 | gsap.registerPlugin(ScrollTriggerPlugin);
7 |
8 | export type ScrollTriggerProps = {
9 | children?: React.ReactNode;
10 | } & gsap.plugins.ScrollTriggerInstanceVars;
11 |
12 | class ScrollTrigger extends Provider {
13 | static displayName = 'ScrollTrigger';
14 |
15 | scrollTrigger: any | null = null;
16 | targets: any = {};
17 |
18 | constructor(props: ScrollTriggerProps) {
19 | super(props);
20 |
21 | this.getPlugin = this.getPlugin.bind(this);
22 | }
23 |
24 | // override and pass registerConsumer to next parent provider
25 | registerConsumer(consumer: any) {
26 | this.context.registerConsumer(consumer);
27 | }
28 |
29 | componentDidMount() {
30 | const { children, ...scrollTrigger } = this.props;
31 |
32 | if (!children) {
33 | this.scrollTrigger = ScrollTriggerPlugin.create(scrollTrigger);
34 | }
35 | }
36 |
37 | componentWillUnmount() {
38 | if (this.scrollTrigger) {
39 | this.scrollTrigger.kill();
40 | }
41 | }
42 |
43 | // componentDidUpdate(prevProps: ScrollTriggerProps) {
44 | // const { trigger } = this.props;
45 | //
46 | // if (trigger !== prevProps.trigger) {
47 | // console.log('prevProps.trigger', prevProps.trigger);
48 | // console.log('trigger', trigger);
49 | // }
50 | // }
51 |
52 | getGSAP() {
53 | return this.scrollTrigger;
54 | }
55 |
56 | getPlugin(props: any, targets: any) {
57 | let { children, trigger: triggerProp, ...scrollTrigger } = props;
58 |
59 | let trigger = triggerProp;
60 |
61 | if (targets instanceof Map) {
62 | if (trigger) {
63 | const target = targets.get(trigger);
64 | if (target) {
65 | trigger = target;
66 | }
67 | } else {
68 | trigger = Array.from(targets.values());
69 | }
70 | } else if (!trigger) {
71 | trigger = targets;
72 | }
73 |
74 | return {
75 | trigger,
76 | ...scrollTrigger,
77 | };
78 | }
79 |
80 | render() {
81 | const { children, ...scrollTrigger } = this.props;
82 | if (!children) {
83 | return null;
84 | }
85 | return this.renderWithProvider(children, {
86 | scrollTrigger,
87 | });
88 | }
89 | }
90 |
91 | export default ScrollTrigger;
92 |
--------------------------------------------------------------------------------
/packages/react-gsap/src/tools/Scroller.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { gsap } from 'gsap';
3 | import { nullishCoalescing } from '../helper';
4 | import Provider from '../Provider';
5 |
6 | export enum TriggerPosition {
7 | top = 'top',
8 | bottom = 'bottom',
9 | }
10 |
11 | export type ScrollerProps = {
12 | children: (progress: number) => React.ReactNode;
13 | heightVh: number;
14 | resolution: number;
15 | triggerPosition: TriggerPosition;
16 | };
17 |
18 | type ScrollerState = {
19 | progress: number;
20 | };
21 |
22 | class Scroller extends Provider {
23 | static displayName = 'ScrollReveal';
24 |
25 | static defaultProps = {
26 | heightVh: 100,
27 | resolution: 100,
28 | triggerPosition: TriggerPosition.bottom,
29 | };
30 |
31 | state: ScrollerState = {
32 | progress: 0,
33 | };
34 |
35 | timeline: any;
36 | heights: number[] = [];
37 | targetRefs: HTMLElement[] = [];
38 | observer: IntersectionObserver | null = null;
39 |
40 | constructor(props: ScrollerProps) {
41 | super(props);
42 | this.heights = this.getTargetHeights(this.props.heightVh);
43 | }
44 |
45 | getTargetHeights(heightVh: number) {
46 | const numberTimes = heightVh / 100;
47 | const numberTargets = Math.ceil(numberTimes);
48 | const numberFull = Math.floor(numberTimes);
49 | const lastHeight = numberTargets === numberFull ? 100 : (numberTimes % numberFull) * 100;
50 | const heights = Array.from({ length: numberTargets }, () => 100);
51 | heights[numberTargets - 1] = lastHeight;
52 | return heights;
53 | }
54 |
55 | componentDidMount() {
56 | this.createTimeline();
57 | this.createIntersectionObserver();
58 | }
59 |
60 | componentWillUnmount() {
61 | this.timeline.kill();
62 | }
63 |
64 | getSnapshotBeforeUpdate() {
65 | // this.targets = [];
66 | return null;
67 | }
68 |
69 | componentDidUpdate(prevProps: ScrollerProps) {
70 | const { children } = this.props;
71 |
72 | // if children change create a new timeline
73 | // TODO: replace easy length check with fast equal check
74 | // TODO: same for props.target?
75 | if (React.Children.count(prevProps.children) !== React.Children.count(children)) {
76 | this.createTimeline();
77 | }
78 | }
79 |
80 | createTimeline() {
81 | if (this.timeline) {
82 | this.timeline.kill();
83 | }
84 |
85 | // init timeline
86 | this.timeline = gsap.timeline({
87 | smoothChildTiming: true,
88 | paused: true,
89 | });
90 |
91 | // add consumers
92 | this.consumers.forEach(consumer => {
93 | const { position } = consumer.props;
94 | this.timeline.add(consumer.getGSAP().play(), nullishCoalescing(position, 0));
95 | });
96 | }
97 |
98 | createIntersectionObserver() {
99 | const { resolution } = this.props;
100 |
101 | const options = {
102 | // root: this.fixedWrapperRef,
103 | root: null,
104 | rootMargin: '0px',
105 | threshold: Array.from({ length: resolution + 1 }, (v, i) => i / resolution),
106 | };
107 |
108 | this.observer = new IntersectionObserver(this.intersectionObserverCallback, options);
109 |
110 | this.targetRefs.forEach(target => {
111 | this.observer && this.observer.observe(target);
112 | });
113 | }
114 |
115 | unobserveAll() {
116 | this.targetRefs.forEach(target => {
117 | this.observer && this.observer.unobserve(target);
118 | });
119 | }
120 |
121 | intersectionObserverCallback = (entries: any) => {
122 | const { triggerPosition } = this.props;
123 | const progresses = Array.from({ length: this.heights.length }, () => 0);
124 | const { heightVh } = this.props;
125 |
126 | for (const entry of entries) {
127 | console.log('rootBounds.height', entry.rootBounds.height);
128 | console.log('boundingClientRect.top', entry.boundingClientRect.top);
129 | console.log('boundingClientRect.height', entry.boundingClientRect.height);
130 | console.log('intersectionRatio', entry.intersectionRatio);
131 | console.log('intersectionRect.top', entry.intersectionRect.top);
132 | console.log('intersectionRect.height', entry.intersectionRect.height);
133 | console.log('isIntersecting', entry.isIntersecting);
134 |
135 | let progress = 0;
136 |
137 | if (triggerPosition === TriggerPosition.top) {
138 | const height = entry.boundingClientRect.height;
139 | const top = entry.boundingClientRect.top;
140 | const position = top <= 0 ? -top : 0;
141 | progress = position / height;
142 | } else if (triggerPosition === TriggerPosition.bottom) {
143 | const height = entry.boundingClientRect.height;
144 | const position = height - Math.max(Math.min(entry.boundingClientRect.top, height), 0);
145 | progress = position / height;
146 | }
147 |
148 | // console.log('progress', progress);
149 |
150 | const key = entry.target.dataset.key;
151 |
152 | progresses[parseInt(key, 10)] = progress;
153 | }
154 |
155 | if (this.emptyProgresses(progresses)) {
156 | // this.setState({ progress: 0 });
157 | return;
158 | }
159 |
160 | const totalProgress = this.getTotalProgress(progresses);
161 | const progress = (totalProgress * 100) / heightVh;
162 |
163 | console.log('progresses', progresses);
164 | console.log('totalProgress', totalProgress);
165 | console.log('progress', progress);
166 |
167 | this.setState({ progress });
168 | };
169 |
170 | getTotalProgress(progresses: number[]) {
171 | // const length = progresses.length;
172 | return progresses.reduceRight((previousValue, currentValue) => {
173 | if (previousValue) {
174 | return Math.min(currentValue || 1, 1) + previousValue;
175 | }
176 | return currentValue;
177 | });
178 | }
179 |
180 | emptyProgresses(progresses: number[]) {
181 | for (const progress of progresses) {
182 | if (progress) {
183 | return false;
184 | }
185 | }
186 | return true;
187 | }
188 |
189 | getGSAP() {
190 | return this.timeline;
191 | }
192 |
193 | render() {
194 | const { children } = this.props;
195 | const { progress } = this.state;
196 |
197 | const wrapper = (
198 | <>
199 | {this.heights.map((height: number, index: number) => (
200 | this.targetRefs.push(target)}
203 | key={index}
204 | data-key={index}
205 | >
206 | {index === 0 ? children(progress) : null}
207 |
208 | ))}
209 | >
210 | );
211 |
212 | return this.renderWithProvider(wrapper);
213 | }
214 | }
215 |
216 | export default Scroller;
217 |
--------------------------------------------------------------------------------
/packages/react-gsap/src/tools/SplitText.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | type SplitWordsProps = {
4 | children: React.ReactNode;
5 | wrapper: React.ReactElement;
6 | delimiter?: string;
7 | };
8 |
9 | type SplitCharsProps = {
10 | children: React.ReactNode;
11 | wrapper: React.ReactElement;
12 | };
13 |
14 | const escapeRegExp = (regExp: string) => {
15 | var specialChars = ['$', '^', '*', '(', ')', '+', '[', ']', '{', '}', '\\', '|', '.', '?', '/'];
16 | var regex = new RegExp('(\\' + specialChars.join('|\\') + ')', 'g');
17 | return regExp.replace(regex, '\\$1');
18 | };
19 |
20 | // TODO: possible or better to output all the refs as one array?
21 | export const SplitWords = React.forwardRef(
22 | ({ children, wrapper, delimiter = ' ' }, ref) => {
23 | if (typeof children !== 'string') {
24 | throw new Error('SplitWords only accepts a string as child.');
25 | }
26 | const words = children.split(new RegExp(`(${escapeRegExp(delimiter)})`, 'g'));
27 | return (
28 | <>
29 | {words.map((word: string, i: number) => {
30 | if (delimiter === ' ' && word === delimiter) {
31 | return ;
32 | }
33 | return React.cloneElement(wrapper, { ref, key: i }, word);
34 | })}
35 | >
36 | );
37 | }
38 | );
39 |
40 | // TODO: possible or better to output all the refs as one array?
41 | export const SplitChars = React.forwardRef(({ children, wrapper }, ref) => {
42 | if (typeof children !== 'string') {
43 | throw new Error('SplitChars only accepts a string as child.');
44 | }
45 | return (
46 | <>
47 | {children
48 | .split(
49 | /(?=(?:[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))/
50 | )
51 | .map((char: string, i: number) => {
52 | // TODO: enhance check for space
53 | if (char === ' ') {
54 | return ;
55 | }
56 | return React.cloneElement(wrapper, { ref, key: i }, char);
57 | })}
58 | >
59 | );
60 | });
61 |
62 | export const SplitLetters = React.forwardRef((props: any, ref) => {
63 | console.warn('Deprecation warning: Use SplitChars instead of SplitLetters');
64 | return ;
65 | });
66 |
--------------------------------------------------------------------------------
/packages/react-gsap/src/types.ts:
--------------------------------------------------------------------------------
1 | export enum PlayState {
2 | play = 'play',
3 | restart = 'restart',
4 | reverse = 'reverse',
5 | restartReverse = 'restartReverse',
6 | stop = 'stop',
7 | stopEnd = 'stopEnd',
8 | pause = 'pause',
9 | resume = 'resume',
10 | }
11 |
--------------------------------------------------------------------------------
/packages/react-gsap/test/blah.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import { Thing } from '../src';
4 |
5 | describe('it', () => {
6 | it('renders without crashing', () => {
7 | const div = document.createElement('div');
8 | ReactDOM.render( , div);
9 | ReactDOM.unmountComponentAtNode(div);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/packages/react-gsap/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src", "types"],
3 | "compilerOptions": {
4 | "module": "esnext",
5 | "lib": ["dom", "esnext"],
6 | "importHelpers": true,
7 | "declaration": true,
8 | "sourceMap": true,
9 | "rootDir": "./src",
10 | "strict": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "moduleResolution": "node",
14 | "baseUrl": "./",
15 | "paths": {
16 | "*": ["src/*", "node_modules/*"]
17 | },
18 | "jsx": "react",
19 | "esModuleInterop": true,
20 | "noImplicitThis": false,
21 | "noUnusedLocals": false,
22 | "noUnusedParameters": false,
23 | "allowSyntheticDefaultImports": true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------