This content originally appeared on Blog and was authored by Blog
A guest post from Rodrigo Hernando, a talented animator/developer who has years of experience solving animation challenges. Rodrigo is a seasoned React developer and he was one of our first moderators, serving the GreenSock community since 2011 with his expert help and friendly charm.
Preface
This guide assumes a basic knowledge of both the GreenSock Animation Platform (GSAP) and React, as well as some common tools used to develop a React app.
As GSAP becomes the de-facto standard for creating rich animations and UI's on the web, developers must learn how to integrate it with other tools like React which has become popular because it allows developers to write their apps in a modular, declarative and re-usable fashion. As a moderator in the GreenSock forums, I've noticed that there are a few common hurdles to getting the two working together seamlessly, like referencing the DOM element appropriately, doing things The React Way, etc. which is why I'm writing this article.
We won't delve into how a React app should be structured since our focus is on using GSAP, but the techniques used throughout this guide follow the official guidelines and have been reviewed by maintainers of the React Transition Group tool. We'll start simple and get more complex toward the end.
How GSAP Works
GSAP basically updates numeric properties of an object many times per second which creates the illusion of animation. For DOM elements, GSAP updates the the inline style
properties.
const myElement = document.getElementById("my-element"); TweenLite.to(myElement, 1, {width: 100, backgroundColor: "red"});
As you can see this means that we need access to the actual DOM node rendered in the document in order to pass it to the TweenLite.to()
method.
How React Works
Explaining how React works is beyond the scope of this article, but let's focus on how React gets the JSX code we write and puts that in the DOM.
<div className="my-class"> Some content here </div>
id
attribute to the element because we use a declarative way to access methods, instances, props and state. It's through the component's (or the application's) state that we can change how things are represented in the DOM. There's no direct DOM manipulation, so typically there's no need to actually access the DOM.
Creating Our First Animation
We'll use the ref
to access the DOM node and the componentDidMount()
lifecycle method of the component to create our first animation, because this will guarantee that the node has been added to the DOM tree and is ready to be passed into a GSAP animation.
class MyComponent extends Component { constructor(props){ super(props); // reference to the DOM node this.myElement = null; // reference to the animation this.myTween = null; } componentDidMount(){ // use the node ref to create the animation this.myTween = TweenLite.to(this.myElement, 1, {x: 100, y: 100}); } render(){ return <div ref={div => this.myElement = div} />; } }
Not that difficult, right? Let's go through the code so we can understand what is happening. First when we create an instance of this class, two properties are added to it: myElement
and myTween
, but both are equal to null. Why? Because at this point the node has not been added to the DOM tree and if we try to pass this node to a GSAP animation, we'll get an error indicating that GSAP cannot tween a null
target.
After the new instance has been initialized, the render()
method runs. In the render method we're using the ref
attribute that is basically a function that has a single parameter – the DOM node being added to the DOM tree. At this point we update the reference to the DOM node created in the class constructor. After that, this reference is no longer null
and can be used anywhere we need it in our component.
Finally, the componentDidMount()
method runs and updates the reference to myTween
with a TweenLite
tween whose target
is the internal reference to the DOM node that should animate. Simple, elegant and very React-way of us!
It is worth mentioning that we could have created a one-run-animation by not creating a reference to the TweenLite tween in the constructor method. We could have just created a tween in the componentDidMount
method and it would run immediately, like this:
componentDidMount(){ TweenLite.to(this.myElement, 1, {x: 100, y: 100}); }
The main benefit of storing a TweenLite tween as a reference in the component, is that this pattern allows us to use any of the methods GSAP has to offer like: play()
, pause()
, reverse()
, restart()
, seek()
, change the speed (timeScale
), etc., to get full control of the animations. Also this approach allows us to create any GSAP animation (TweenLite, TweenMax, TimelineLite, etc.) in the constructor. For example, we could use a timeline in order to create a complex animation:
constructor(props){ super(props); this.myElement = null; this.myTween = TimelineLite({paused: true}); } componentDidMount(){ this.myTween .to(this.myElement, 0.5, {x: 100}) .to(this.myElement, 0.5, {y: 100, rotation: 180}) .play(); }
With this approach we create a paused Timeline in the constructor and add the individual tweens using the shorthand methods. Since the Timeline was paused initially, we play it after adding all the tweens to it. We could also leave it paused and control it somewhere else in our app. The following example shows this technique:
Simple Tween Demo
Animating a Group of Elements
One of the perks of using React is that allows us to add a group of elements using the array.map()
method, which reduces the amount of HTML we have to write. This also can help us when creating an animation for all those elements. Let's say that you want to animate a group of elements onto the screen in a staggered fashion. It's simple:
constructor(props){ super(props); this.myTween = new TimelineLite({paused: true}); this.myElements = []; } componentDidMount(){ this.myTween.staggerTo(this.myElements, 0.5, {y: 0, autoAlpha: 1}, 0.1); } render(){ return <div> <ul> {elementsArray.map((element, index) => <li key={element.id} ref={li => this.myElements[index] = li} > {element.name} </li>)} </ul> </div>; }
This looks a bit more complex but we're using the same pattern to access each DOM node. The only difference is that instead of using a single reference for each element, we add each element to an array. In the componentDidMount()
method we use TimelineLite.staggerTo() and GSAP does its magic to create a staggered animation!
Multiple Elements Demo
Creating a Complex Sequence
We won't always get all the elements in an array so sometimes we might need to create a complex animation using different elements. Just like in the first example we store a reference in the constructor for each element and create our timeline in the componentDidMount()
method:
Timeline Sequence Demo
Note how in this example we use a combination of methods. Most of the elements are stored as an instance property using this.element = null
, but also we're adding a group of elements using an array.map()
. Instead of using the map()
callback to create tweens in the timeline (which is completely possible), we're adding them to an array that is passed in the staggerFrom() method to create the stagger effect.
Animating Via State
The most commonly used pattern to update a React app is through changing the state of its components. So it's easy to control when and how elements are animated based on the app state. It's not very difficult to listen to state changes and control a GSAP animation depending on state, using the componentDidUpdate() lifecycle method. Basically we compare the value of a state property before the update and after the update, and control the animation accordingly.
componentDidUpdate(prevProps, prevState) { if (prevState.play !== this.state.play) { this.myTween.play(); } }
Control Through State Demo
In this example we compare the value of different state properties (one for each control method implemented in the component) to control the animation as those values are updated. It's important to notice that this example is a bit convoluted for doing something that can be achieved by calling a method directly in an event handler (such as onClick
). The main idea is to show the proper way of controlling things through the state.
A cleaner and simpler way to control an animation is by passing a prop
from a parent component or through an app state store such as Redux or MobX. This modal samples does exactly that:
// parent component <ModalComponent visible={this.state.modalVisible} close={this.setModalVisible.bind(null, false)} /> // ModalComponent constructor(props){ super(props); this.modalTween = new TimelineLite({ paused: true }); } componentDidMount() { this.modalTween .to(this.modalWrap, 0.01, { autoAlpha: 1 }) .to(this.modalDialog, 0.25, { y: 50, autoAlpha: 1 }, 0) .reversed(true) .paused(false); } componentDidUpdate(){ this.modalTween.reversed(!this.props.visible); }
As you can see the modal animation is controlled by updating the visible
prop passed by its parent, as well as a close method passed as a prop. This code is far simpler and reduces the chance of error.
State Modal Demo
Using React Transition Group
React Transition Group(RTG) is a great tool that allows another level of control when animating an element in a React app. This is referred to as the capacity to mount and unmount either the element being animated or an entire component. This might not seem like much when animating a single image or a div, but this could mean a significant performance enhancement in our app in some cases.
SIMPLE TRANSITION DEMO
In this example the <Transition>
component wraps the element we want to animate. This element remains unmounted while it's show
prop is false. When the value changes to true
, it is mounted and then the animation starts. Then when the prop is set to false
again, another animation starts and when this is completed it can also use the <Transition>
component to wrap the entire component.
RTG also provides the <TransitionGroup>
component, which allows us to control a group of <Transition>
components, in the same way a single <Transition>
component allows to control the mounting and unmounting of a component. This is a good alternative for animating dynamic lists that could have elements added and/or removed, or lists based on data filtering.
Transition Group Demo
<Transition timeout={1000} mountOnEnter unmountOnExit in={show} addEndListener={(node, done) => { TweenLite.to(node, 0.35, { y: 0, autoAlpha: show ? 1 : 0, onComplete: done, delay: !show ? 0 : card.init ? props.index * 0.15 : 0 }); }} >
In this example we use the addEndListener() callback from the <Transition>
component. This gives us two parameters, the node
element being added in the DOM tree and the done
callback, which allows to control the inner state of the <Transition>
component as the element is mounted and unmounted.
The entire animation is controlled by the in
prop, which triggers the addEndListener()
and ultimately the animation. You may notice that we're not creating two different animations for the enter/exit state of the component. We create a single animation that uses the same DOM node and the same properties. By doing this, GSAP's overwrite manager kills any existing animation affecting the same element and properties, giving us a seamless transition between the enter and exit animations.
Finally, using RTG allows us for a more fine-grained code, because we can use all the event callbacks provided by GSAP (onStart
, onUpdate
, onComplete
, onReverse
, onReverseComplete
) to run all the code we want, before calling the done
callback (is extremely important to notify that the animation has completed).
Animating Route Changes
Routing is one of the most common scenarios in a React app. Route changes in a React app means that an entirely different view is rendered depending on the path in the browser's address bar which is the most common pattern to render a completely different component in a route change. Obviously animating those changes gives a very professional look and feel to our React apps. Rendering a new component based on a route change means that the component of the previous route is unmounted and the one for the next route is mounted. We already covered animating components animations tied to mount/unmount using the <Transition>
component from RTG, so this is a very good option to animate route changes.
<BrowserRouter> <div> <Route path="/" exact> { ({ match }) => <Home show={match !== null} /> } </Route> <Route path="/services"> { ({ match }) => <Services show={match !== null} /> } </Route> <Route path="/contact"> { ({ match }) => <Contact show={match !== null} /> } </Route> </div> </BrowserRouter>
This main component uses React Router's <BrowserRouter>
and <Route>
and checks the match
object passed as a prop to every <Route>
component, while returning the component that should be rendered for each URL. Also we pass the show
property to each component, in the same way we did in the transition example.
<Transition unmountOnExit in={props.show} timeout={1000} onEnter={node => TweenLite.set(node, startState)} addEndListener={ (node, done) => { TweenLite.to(node, 0.5, { autoAlpha: props.show ? 1 : 0, y: props.show ? 0 : 50, onComplete: done }); }} >
As you can see, the code is basically the same used to animate a single component; the only difference is that now we have two animations happening in different components at the same time.
Route Animation Demo
It's worth noting that the animations used in this example are quite simple but you can use any type of animation even complex, nested animations.
As you can see by now, using GSAP and React can play together nicely. With all the tools and plugins GSAP has to offer the sky is the limit for creating compelling and amazing React applications!
FAQ
New to GSAP? Check out the Getting Started Guide. Got questions? Head over to the GreenSock forums where there's a fantastic community of animators.
Acknowledgments
I'd like to thank the three developers that took time from their work and lives to review this guide as well as the samples in it. I couldn't have done this without their help and valuable input. Please be sure to follow them:
- Xiaoyan Wang: A very talented React developer. While Xiaoyan doesn't have a very active online social life (Twitter, Facebook, etc), you can follow what he does on GitHub.
- Jason Quense: One of the maintainers of React Transition Group and part of the React Bootstrap Team. Jason also collaborates in many other React-related projects. Check Jason's GitHub profile for more info.
- Matija Marohnić: The most active contributor and maintainer of React Transition Group and Part of the Yeoman Team. Matija also contributes in a lot of React-related projects as well as many other open source software. Be sure to follow Matija in GitHub and Twitter.
This content originally appeared on Blog and was authored by Blog
Blog | Sciencx (2018-10-01T07:00:00+00:00) Getting Started: React and GSAP Animations. Retrieved from https://www.scien.cx/2018/10/01/getting-started-react-and-gsap-animations/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.