Skip to content

Instantly share code, notes, and snippets.

@jonkwheeler
Last active May 2, 2024 05:46
Show Gist options
  • Save jonkwheeler/377ace055376b6537acbce00ed3be4e1 to your computer and use it in GitHub Desktop.
Save jonkwheeler/377ace055376b6537acbce00ed3be4e1 to your computer and use it in GitHub Desktop.
React: Animation component using GSAP's SplitText plugin to reveal text on a page
// @flow
import { PureComponent, type Node, Fragment } from 'react';
import { TimelineLite } from 'gsap';
import SplitText from 'Lib/gsap-bonus/umd/SplitText';
import Waypoint from 'react-waypoint';
type RevealTextProps = {
children: Node,
waypointTopOffset: string,
waypointBottomOffset: string,
duration: number,
stagger: number,
forwardSpeed: number,
reverseSpeed: number
};
type RevealTextState = {
waypointActive: boolean
};
export default class RevealText extends PureComponent<RevealTextProps, RevealTextState> {
static defaultProps = {
duration: 0.5,
stagger: 0.2,
waypointTopOffset: '0%',
waypointBottomOffset: '20%',
forwardSpeed: 1,
reverseSpeed: 0.5
};
constructor(props) {
super(props);
this.revealText = this.revealText.bind(this);
this.destroySplitText = this.destroySplitText.bind(this);
this.mySplitText = null;
this.splitTextTimeline = null;
}
state = { waypointActive: true };
componentDidMount() {
this.mySplitText = new SplitText(this.splitThisText, { type: 'words, lines' });
this.splitTextTimeline = new TimelineLite({ immediateRender: false });
this.splitTextTimeline
.addLabel('start')
.set(this.mySplitText.lines, { perspective: 400, overflow: 'hidden' }, 'start')
.set(this.splitThisText, { opacity: 1 }, 'start')
.staggerFromTo(
this.mySplitText.words,
this.props.duration,
{
y: '100%',
transformOrigin: 'top center -150'
},
{
y: '0%',
force3D: true
},
this.props.stagger,
'start'
);
}
componentWillUnmount() {
this.destroySplitText();
}
destroySplitText() {
this.setState({ waypointActive: false });
this.mySplitText.revert();
}
revealText(direction: string) {
if (direction === 'enter') {
this.splitTextTimeline.play(0).timeScale(this.props.forwardSpeed);
} else {
this.splitTextTimeline.reverse().timeScale(this.props.reverseSpeed);
}
}
render() {
return (
<Fragment>
{this.state.waypointActive && (
<Waypoint
onEnter={this.revealText.bind(this, 'enter')}
onLeave={this.revealText.bind(this, 'leave')}
topOffset={this.props.waypointTopOffset}
bottomOffset={this.props.waypointBottomOffset}
scrollableAncestor={window}
/>
)}
<div
ref={x => {
this.splitThisText = x;
}}
style={{ opacity: 0 }}
>
{this.props.children}
</div>
</Fragment>
);
}
}
@jonkwheeler
Copy link
Author

2018-06-26 11_51_05

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment