Skip to content

Commit 8a9b8c0

Browse files
author
Jan Fischer
committed
feat: add ScrollTrigger component
1 parent 313b048 commit 8a9b8c0

File tree

7 files changed

+304
-34
lines changed

7 files changed

+304
-34
lines changed
Lines changed: 163 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
1-
import React from 'react';
1+
import React, {
2+
forwardRef,
3+
MutableRefObject,
4+
useEffect,
5+
useImperativeHandle,
6+
useRef,
7+
useState,
8+
} from 'react';
29
import styled from 'styled-components';
3-
import { Tween, SplitWords, SplitLetters, Controls } from 'react-gsap';
4-
import { gsap } from 'gsap';
5-
import { ScrollTrigger } from 'gsap/ScrollTrigger';
6-
gsap.registerPlugin(ScrollTrigger);
10+
import {
11+
Tween,
12+
Timeline,
13+
SplitWords,
14+
SplitLetters,
15+
Controls,
16+
ScrollTrigger,
17+
PlayState,
18+
SplitChars,
19+
} from 'react-gsap';
720

821
const Container = styled.div`
922
width: 100%;
@@ -26,26 +39,151 @@ const FadeIn = ({ children }: { children: React.ReactNode }) => (
2639
<Tween from={{ opacity: 0 }}>{children}</Tween>
2740
);
2841

29-
const TweenComponent = () => (
30-
<TweenStyled>
31-
<Container className="trigger">
32-
<Tween
33-
to={{
34-
x: '500px',
35-
scrollTrigger: {
36-
trigger: '.trigger',
37-
start: 'top center',
38-
end: 'bottom center',
39-
scrub: 0.5,
40-
markers: true,
41-
pin: true,
42-
},
43-
}}
42+
const TargetWithNames = forwardRef((props, ref: any) => {
43+
const div1 = useRef(null);
44+
const div2 = useRef<MutableRefObject<any>[]>([]);
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+
<div ref={trigger}>
55+
<div ref={div1}>first</div>
56+
<SplitChars
57+
ref={(charRef: MutableRefObject<any>) => div2.current.push(charRef)}
58+
wrapper={<span style={{ display: 'inline-block' }} />}
4459
>
45-
<Square className="square">This element gets tweened</Square>
46-
</Tween>
47-
</Container>
48-
</TweenStyled>
49-
);
60+
second
61+
</SplitChars>
62+
<div ref={div3}>third</div>
63+
</div>
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+
<TweenStyled>
78+
<ScrollTrigger trigger=".trigger">
79+
<Tween
80+
to={{
81+
x: '500px',
82+
}}
83+
>
84+
<Square className="square">This element gets tweened</Square>
85+
</Tween>
86+
</ScrollTrigger>
87+
88+
<ScrollTrigger trigger={trigger}>
89+
<Tween
90+
to={{
91+
x: '500px',
92+
}}
93+
>
94+
<Square className="square">This element gets tweened by ref</Square>
95+
</Tween>
96+
</ScrollTrigger>
97+
98+
<ScrollTrigger
99+
start="top center"
100+
end="400px center"
101+
scrub={0.5}
102+
markers={true}
103+
pin={true}
104+
once={false}
105+
>
106+
<Tween
107+
to={{
108+
x: '600px',
109+
}}
110+
>
111+
<Square className="trigger" ref={triggerRef}>
112+
This element is the trigger
113+
</Square>
114+
</Tween>
115+
</ScrollTrigger>
116+
117+
<ScrollTrigger>
118+
<Tween
119+
to={{
120+
x: '500px',
121+
}}
122+
>
123+
<Square className="square">This element gets tweened</Square>
124+
</Tween>
125+
<Tween
126+
to={{
127+
x: '500px',
128+
}}
129+
>
130+
<Square className="square">This element gets tweened</Square>
131+
</Tween>
132+
</ScrollTrigger>
133+
134+
<ScrollTrigger
135+
trigger="trigger"
136+
start="top center"
137+
end="400px center"
138+
scrub={0.5}
139+
markers={true}
140+
pin={true}
141+
>
142+
<Timeline target={<TargetWithNames />}>
143+
<Tween
144+
to={{
145+
x: '500px',
146+
}}
147+
target="div1"
148+
/>
149+
<Tween
150+
to={{
151+
x: '200px',
152+
}}
153+
stagger={0.2}
154+
target="div2"
155+
/>
156+
<Tween
157+
to={{
158+
x: '500px',
159+
}}
160+
target="div3"
161+
/>
162+
</Timeline>
163+
</ScrollTrigger>
164+
165+
<ScrollTrigger />
166+
167+
{/*<Container className="trigger">*/}
168+
{/* <Tween*/}
169+
{/* to={{*/}
170+
{/* x: '500px',*/}
171+
{/* scrollTrigger: {*/}
172+
{/* trigger: '.trigger',*/}
173+
{/* start: 'top center',*/}
174+
{/* end: 'bottom center',*/}
175+
{/* scrub: 0.5,*/}
176+
{/* markers: true,*/}
177+
{/* pin: true,*/}
178+
{/* },*/}
179+
{/* }}*/}
180+
{/* >*/}
181+
{/* <Square className="square">This element gets tweened</Square>*/}
182+
{/* </Tween>*/}
183+
{/*</Container>*/}
184+
</TweenStyled>
185+
</>
186+
);
187+
};
50188

51189
export default TweenComponent;

packages/react-gsap/src/Provider.tsx

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,68 @@
11
import React from 'react';
22

3+
type RegisteredPlugins = 'scrollTrigger';
4+
type Plugin = (targets: any) => any;
5+
type Plugins = { [key in RegisteredPlugins]: Plugin } | {};
6+
37
export type ContextProps = {
48
registerConsumer: (consumer: any) => void;
9+
plugins?: Plugins;
10+
getPlugins: (plugins?: Plugins, targets?: any) => any;
511
};
612

7-
export const Context = React.createContext<ContextProps>({ registerConsumer: () => {} });
13+
export const Context = React.createContext<ContextProps>({
14+
registerConsumer: () => {},
15+
getPlugins: () => {},
16+
plugins: {},
17+
});
818

919
abstract class Provider<T, S = {}> extends React.Component<T, S> {
20+
static contextType = Context;
21+
1022
consumers: any[];
23+
plugins?: Plugins;
1124

1225
constructor(props: T) {
1326
super(props);
1427
this.consumers = [];
28+
this.plugins = {};
1529

1630
this.registerConsumer = this.registerConsumer.bind(this);
1731
this.getContextValue = this.getContextValue.bind(this);
32+
this.getPlugin = this.getPlugin.bind(this);
33+
this.getPlugins = this.getPlugins.bind(this);
1834
this.renderWithProvider = this.renderWithProvider.bind(this);
1935
}
2036

2137
registerConsumer(consumer: any) {
2238
this.consumers.push(consumer);
2339
}
2440

25-
getContextValue() {
41+
getContextValue(plugin: Plugins = {}) {
2642
return {
2743
registerConsumer: this.registerConsumer,
44+
// plugins: { ...this.context.plugins, ...plugin },
45+
plugins: plugin,
46+
getPlugins: this.getPlugins,
2847
};
2948
}
3049

31-
renderWithProvider(output: any) {
32-
return <Context.Provider value={this.getContextValue()}>{output}</Context.Provider>;
50+
getPlugin(props: any, targets: any) {
51+
return {};
52+
}
53+
54+
getPlugins(plugins?: Plugins, targets?: any) {
55+
return Object.keys(plugins ?? {}).reduce((acc, plugin) => {
56+
if (Object.prototype.hasOwnProperty.call(plugins, plugin)) {
57+
// @ts-ignore
58+
return { ...acc, [plugin]: this.getPlugin(plugins[plugin], targets) };
59+
}
60+
return acc;
61+
}, {});
62+
}
63+
64+
renderWithProvider(output: any, plugin?: Plugins) {
65+
return <Context.Provider value={this.getContextValue(plugin)}>{output}</Context.Provider>;
3366
}
3467
}
3568

packages/react-gsap/src/Timeline.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ export type TimelineProps = {
4343

4444
class Timeline extends Provider<TimelineProps> {
4545
static displayName = 'Timeline';
46-
static contextType = Context;
4746

4847
timeline: any;
4948
targets: Targets = new Map();
@@ -116,11 +115,14 @@ class Timeline extends Provider<TimelineProps> {
116115
this.timeline.kill();
117116
}
118117

118+
const plugins = this.context?.getPlugins(this.context?.plugins, this.targets) ?? {};
119+
119120
// init timeline
120121
this.timeline = gsap.timeline({
121122
smoothChildTiming: true,
122123
paused: getInitialPaused(playState),
123124
...vars,
125+
...plugins,
124126
});
125127

126128
if (labels) {

packages/react-gsap/src/Tween.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ class Tween extends React.Component<TweenProps, {}> {
176176
}
177177

178178
if (this.props.children) {
179-
this.tween = getTweenFunction(this.targets, this.props);
179+
this.tween = getTweenFunction(this.targets, this.props, this.context);
180180
} else {
181181
// why this is needed?
182182
this.tween = () => {};

packages/react-gsap/src/helper.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React from 'react';
33
import { PlayState } from './types';
44
import { TimelineProps } from 'Timeline';
55
import { TweenProps } from 'Tween';
6+
import { ContextProps } from 'Provider';
67

78
if (!String.prototype.startsWith) {
89
String.prototype.startsWith = function(searchString, position) {
@@ -56,7 +57,8 @@ const getInitialPaused = (playState?: PlayState) => {
5657

5758
const getTweenFunction = (
5859
targets: any,
59-
props: TweenProps | TimelineProps
60+
props: TweenProps | TimelineProps,
61+
context?: ContextProps
6062
): gsap.core.Tween | gsap.core.Timeline => {
6163
const {
6264
children,
@@ -87,6 +89,7 @@ const getTweenFunction = (
8789

8890
let tweenFunction: gsap.core.Tween | gsap.core.Timeline;
8991
const paused = getInitialPaused(playState);
92+
const plugins = context?.getPlugins(context?.plugins, targets) ?? {};
9093

9194
if (from && to) {
9295
// special props like paused always go in the toVars parameter
@@ -96,11 +99,12 @@ const getTweenFunction = (
9699
paused,
97100
...to,
98101
...vars,
102+
...plugins,
99103
});
100104
} else if (to) {
101-
tweenFunction = gsap.to(targets, { stagger, duration, paused, ...to, ...vars });
105+
tweenFunction = gsap.to(targets, { stagger, duration, paused, ...to, ...vars, ...plugins });
102106
} else {
103-
tweenFunction = gsap.from(targets, { stagger, duration, paused, ...from, ...vars });
107+
tweenFunction = gsap.from(targets, { stagger, duration, paused, ...from, ...vars, ...plugins });
104108
}
105109

106110
// if multiple tweens (stagger), wrap them in a timeline

packages/react-gsap/src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export { default as Tween } from './Tween';
22
export { default as Timeline } from './Timeline';
33
export { default as Reveal } from './tools/Reveal';
44
// export { default as Scroller } from './tools/Scroller';
5+
export { default as ScrollTrigger } from './tools/ScrollTrigger';
56
export { SplitWords, SplitChars, SplitLetters } from './tools/SplitText';
67
export { default as Controls } from './tools/Controls';
78
export { PlayState } from './types';

0 commit comments

Comments
 (0)