Jump to content
Search Community

Animated Page Transitions in React using GSAP

mb2233 test
Moderator Tag

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

Hi guys. Just wondering if it would be possible to use GSAP for animating simple page transitions in React?

 

I have my index.js file rendering my app: 

// React Common Modules
import React from 'react';
import ReactDOM from 'react-dom';
import { TransitionGroup, CSSTransition } from 'react-transition-group'
import { BrowserRouter, Switch, Route, Link, withRouter } from 'react-router-dom'
import thunkMiddleware from 'redux-thunk';
import createLogger from 'redux-logger';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import Reducer from './Reducers/Reducer';

const loggerMiddleware = createLogger();

const createStoreWithMiddleware = applyMiddleware(
  thunkMiddleware, // Middleware for dispatch()
  loggerMiddleware // Middleware for loging
)(createStore);

let store = createStoreWithMiddleware(Reducer);

// Main SCSS
import './index.scss';

// Root React Component
import App from './App';


ReactDOM.render(
  <BrowserRouter>
    <App/>
  </BrowserRouter>,
  document.getElementById('root')
);

 

Then I have my app.js 

// React Common Modules
import React, { Component } from "react";
// React Router
import { BrowserRouter, Switch, Route, Link, withRouter } from 'react-router-dom'
import { TransitionGroup, CSSTransition } from 'react-transition-group'
import 'gsap/TweenLite'
import 'gsap/CSSPlugin'
import 'gsap/EasePack'
import 'gsap/TimelineLite'

// Own Modules
import { DefaultPage } from "./Pages/";
import { AboutPage } from "./Pages/";

class App extends Component {

  constructor(props) {
    super(props);
  }

  render() {
    return (
      <TransitionGroup>
        <CSSTransition key={location.key} classNames='fade' timeout={1000}>
          <Switch location={this.props.location}>
            <Route exact path='/' component={DefaultPage} />
            <Route exact path='/about' component={AboutPage} />
          </Switch>
        </CSSTransition>
      </TransitionGroup>
    );
  }
}

export default App;

 

And then I have a SideMenu component with all my <Link> 's. Everything is working fine, I just can't get it to create a transition in between the routes, which is where I'd like to use GSAP. The timeout simply doesn't work.

 

Here's the scenario I'm trying to accomplish: 

User is on the Landing Page

Clicks /about link

Color slides in from the left and covers the entire screen (using GSAP) while the landing page is still there beneath the color.

The landing page leaves behind the color.

The color slides out.

About page is revealed. 

 

Does anyone know how to do this? :)

 

Thanks! 

 

Link to comment
Share on other sites

You can definitely use GSAP for React animations - lots of people do. Unfortunately I'm not a React guy so I can't be much help. However, @Rodrigo might - he's the resident React/GSAP pro :)

 

I know Sarah Drasner ( @sdrasner ) is another great resource for animating things in React with GSAP, though she's rarely in these forums.

 

I didn't see any GSAP code in your sample code at all. Were you just asking how/where to add it (to replace the CSSTransition stuff)? 

  • Like 1
Link to comment
Share on other sites

Hi. 

 

Thanks for getting back to me :)

 

Yes, that's exactly what I was hoping someone could help me with, if it's possible. Either replace the CSSTransition stuff or merge the GSAP code into it, to make smooth page transitions :) 

 

I just don't know how to do it, or if it's even possible during routes. 

 

But thanks!  

Link to comment
Share on other sites

Hi,

 

Well since you're using react router and you have an overlay covering the entire content of the site perhaps using transition group with GSAP might not be necessary. The main question is this: Is the color overlay transparent in any degree or solid color?. Depending on the possible answers, these are the scenarios.

 

Case: Solid Color Overlay

 

The color overlay hides all the content, so there's actually no need to use transition group in order to animate each route's component's mount/unmount. Just use GSAP to animate the color overlay and using an onComplete callback, use history (a dependency used by react router and passed as a property to every component in a <Route /> wrapper) to push the next route. In this scenario the only catch is that the overlay component has to reside inside of a route (you can put it in the root route "/" or wrap it in a <Route /> component), which shouldn't be too complicated since I'm assuming it has a fixed position and a z-index that puts it on top of everything. It could be like this:

 

Overlay component, as you can see the idea is to render it in every route, that's why there's no exact attribute in the Route component. Also the Route component is passing it's own history property to the overlay component

class App extends Component {
  
  render(){
    reuturn <div>
      <Route path="/" render={ props => <ColorOverlay history={props.history} /> } />
      // the rest of the routes here
    </div>;
  }
  
}

 

Color overlay component

import { connect } from "react-redux";
import { toggleOverlay } from "./actions"; // depending on your actions path

class ColorOverlay extends Component {
  constructor(){
    super();
    this.overlayTween = null;
    this.overlayElement = null;
    
    this.overlayTweenComplete = this.overlayTweenComplete.bind(this);
  }
  
  overlayTweenComplete(){
    const { history, targetRoute, toggleOverlay } = this.props;
    // here's all the magic
    // at this point the overlay is covering all the content
    // so we can change the route using history passed in the props
    history.push(targetRoute);
    // now the route has changed and we can reverse the animation
    // revealing the new rendered component
    toggleOverlay(false);
  }
  
  componentDidMount(){
    this.overlayTween(this.overlayElement, 1, {/*config here*/, onComplete:this.overlayTweenComplete, paused:true});
  }
  
  // we're updating the visibility via redux, so we look for
  // updates in the props
  componentDidUpdate(prevProps){
    const { overlayVisible } = this.props;
    // check if the visible prop is the one that changed
    if ( prevProps.overlayVisible !== overlayVisible ) {
      overlayVisible ? this.overlayTween.play() : this.overlayTween.reverse();
    }
  }
  
  render(){
    return <div>/*your overlay html here*/</div>;
  }
  
}

const mapStateToProps = state => ({
  overlayVisible: state.overlayVisible,
  targetRoute: state.targetRoute
});

const overlayActions = { toggleOverlay };

export default connect( mapStateToProps, overlayActions )( ColorOverlay );

 

In the snippet I'm making some assumptions that is worth clearing up. First I'm assuming that since you want an overlay over the content using react router's <Link> or <NavLink> is not necessary for every button that should trigger that functionality. If you need a button, link or menu item to not use this overlay transition, then use the normal way of doing that with react router. Second you should have an action creator to toggle the overlay visible property in the store and the target route. The overlay visible is just a boolean and the target route is the string of the route to push into react router, so it should be in this format "/about", "/products", etc. So instead of using a link or navlink with the to="" attribute to change the route, just use an onClick event to update the target route first and then play the overlay animation:

 

import { connect } from "react-redux";
import { toggleOverlay, updateTargetRoute } from "./actions"; // depending on your actions path

class MainMenu extends Component {
  
  constructor(){
    super();
    this.buttonClickHandler = this.buttonClickHandler.bind(this);
  }
  
  buttonClickHandler(target){
    const { toggleOverlay, updateTargetRoute } = this.props;
    // update the route in redux
    updateTargetRoute(target);
    // now that the target is updated toggle the visible state
    toggleOverlay(true);
  }

  render(){
    return <div>
      <button onClick={this.buttonClickHandler.bind(null, '/')}>Home</button>
      <button onClick={this.buttonClickHandler.bind(null, '/about')}>About Us</button>
      <button onClick={this.buttonClickHandler.bind(null, '/products')}>Products</button>
    </div>;
  }
}

const menuActions = { toggleOverlay, updateTargetRoute };

export default connect( null, menuActions )( MainMenu );

(the syntax highlighter is messing up the color, but the code should work).

The reason to control both the play state of the tween and route of the app via redux is that it could be a slight delay while setting the target route and also the fact that if you need the value elsewhere is already in the store.

 

Something like this should be able to animate the overlay before changing the routes and then remove the overlay after.

 

Case: Transparent Color Overlay

 

In this case the content under the overlay will be visible so my guess is that you might need to actually animate one route being unmounted and the other route being mounted. If that's the case take a look at this sample (codepen doesn't have the features to import and mimic real work flows so I use codesandbox for more complex React & Gsap samples):

 

https://codesandbox.io/s/mqy3mmznn

 

Happy Tweening!!!

  • Like 3
  • Thanks 1
Link to comment
Share on other sites

Hi Rodrigo. 


Thank you so much for taking your time to share that! Although I'm having a few issues:

I'm getting a: 

Module build failed: SyntaxError: Unexpected token, expected ,

On the

<button onClick={this.buttonClickHandler.bind(null, '/'}>Home</button>

 

Second: 

What do you mean with this: 

// depending on your actions path

What needs to be in my actions file?

 

Thanks! Really appreciate it! :) 

  • Like 1
Link to comment
Share on other sites

Ahh, sorry missing closing parenthesis there. It should be:

 

<button onClick={this.buttonClickHandler.bind(null, '/')}>Home</button>

 

By the actions path I mean, that since you're using redux you have actions and action creators. Normally in small projects you put all your actions and action creators in one file and export each one as a named export. In larger projects people like to ether put each component, it's own reducer and actions in separate folders, or create a components folder, an actions folders and a reducers folder to place each components actions and reducers and export them via an index.js file.

 

I'm assuming that you have some degree of experience working with redux and react-redux. If you're not very familiar with it you can check this resources:

 

https://redux.js.org/faq/code-structure

 

https://medium.com/front-end-hacking/the-three-pigs-how-to-structure-react-redux-application-67f5e3c68392

 

https://marmelab.com/blog/2015/12/17/react-directory-structure.html

 

https://egghead.io/courses/getting-started-with-redux

 

The egghead course is by Dan Abramov himself (the creator of redux). He goes kind of fast but the concepts are very clear though.

 

Happy Tweening!!!

  • Like 3
Link to comment
Share on other sites

Can't believe I didn't see that one myself. :? But thanks! Regarding the actions, I don't have THAT much experience with Redux. I just know what it does overall, but I'm using a Boilerplate, so that's why all the Redux stuff is there. What do I put in the actions folder considering the Page transition code you posted above?  :)

 

Also, I'm getting the following errors after I added the code: 

React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Check the render method of `Route`.
    in Route (created by App)
    in div (created by App)
    in App
    in Router (created by BrowserRouter)
    in BrowserRouter

Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Check the render method of `Route`.

 

Why is that? :)



Again, thank you for taking your time with this! It's really helpful. 

 

I'll give you some coding credit once my Portfolio is done and dusted. 

  • Like 1
Link to comment
Share on other sites

Hi,

 

Honestly going through all the motions to teach you how to work with redux is out of my capacities in terms of the required time and where exactly you are right now in terms of your coding knowledge.

 

Redux basically let's you rescind, as much as possible, of using component's states in order to keep reference and track of different properties that might change during the app's life cycle. Before redux (and also is worth mentioning it's predecessors Flux and ReFlux) and mobx, a React app had references in the states of different components. The issue was that when you needed to access the state of a sibling component all hell was unleashed, because you needed a method from the parent component to be called in Child A in order to pass that property to the parent and then to Child B. Of course the deeper a component was in the app's tree, the bigger the unleashed hell. It was coding nightmare. Enter redux. Redux is just an object (store) with all the properties you want to keep track and it's accessible via the connect high order function from react redux in all the components of the app that you need. The point is that in order to add and update the property in the store you need actions and actions creators. Those are added to the component's props so they can be accessed and used easily.

 

I strongly recommend you to go through the egghead course (I believe is free) and redux's official docs in order to get a better grasp of how to work with redux and react, is really not that complex.

 

Regarding the error is fairly simple. You're trying to render something that comes as undefined. Similar to this:

class Route extends Component {
  
  render(){
    return <div>
      // some component here, most likely a function is returning undefined
    </div>;
  }
  
}

 

In the comment I mention a function, but also could be a ternary operator or some iteration of a map array helper. Without the entire code and the data structure is impossible to tell. A common scenario is that, since the render method can be called more than once as the state and props are updated (mostly during async code) an array could be empty or something else could be coming as undefined, therefore passing that to the render method which ultimately returns that error. You can use chrome's debugger to put a break point in that render method in order to check the call stack and the scope or use console.log() in the render method, before the return statement of course:

 

class Route extends Component {
  
  render(){
    console.log(/*check props and state here in order to track what could be returning as undefined*/);
    return <div>
      // some component here, most likely a function is returning undefined
    </div>;
  }
  
}

 

Happy tweening!!!

  • Like 3
Link to comment
Share on other sites

  • 4 months later...

Hi and welcome to the GreenSock forums.

 

The first thing you're missing, that has nothing to do with GSAP, is the fact that you're not passing the in property to the components or DOM nodes you want to animate. That's the most important thing a <Transition> tag needs to work.

 

Second, you don't need to wrap every <Transition> inside a <TransitionGroup> tag. Use a <TransitionGroup> tag outside your array.map() helper to add each <Transition> to it:

 

generateCards = () =>
  this.state.items.map(item => (
    <Card
      key={dasherize(item.name.toLowerCase())}
      {...item}
    />
));

// in the render method
<div className="app-container">
  <TransitionGroup>
    {this.generateCards()}
  </TransitionGroup>
</div>

 

Then get rid of the componentWillReceiveProps lifecycle method because is being deprecated and is considered unsafe:

 

https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops

 

Also this methods you have don't exist in the lifecycle of a component: componentWillEnter and componentWillLeave. By adding them in your code you're creating them and adding them to the component's instance, but nothing more, those are not being executed unless you do it manually, so is best to remove them. Here is the documentation regarding components and their lifecycle:

 

https://reactjs.org/docs/react-component.html

 

Finally this sample is somehow similar to what you're doing (without the filtering of course, but that shouldn't be too complicated to achieve):

 

https://codesandbox.io/s/7lp1q8wba

 

The relevant code starts in line 100, so don't pay too much attention to the code above that line. From line 100 below is what you need to check. Also take a look at the API of react transition group:

 

https://reactcommunity.org/react-transition-group/transition

 

http://reactcommunity.org/react-transition-group/transition-group/

 

Happy tweening!!

  • Like 4
  • Thanks 1
Link to comment
Share on other sites

much thanks @Rodrigo. was try use https://github.com/azazdeaz/react-gsap-enhancer follow http://azazdeaz.github.io/react-gsap-enhancer/#/demo/using-transition-group?_k=fnq3qj but looks like it use older api. i thought enhancer was "best practice", but you example no use it. i get it to work now not use enhancer: https://codesandbox.io/s/61r70vyz1r

 

enhancer not needed? any other thing about my script could be better? i try to learn best ways.

 

again thanks!

  • Like 1
Link to comment
Share on other sites

Hey everyone. It's a very great topic. I am new to GSAP and very interested in. I have this code:

 

render() {
    return (
      <Route
        render={({ location }) => (
          <TransitionGroup>
            <Transition
              key={location.key}
              timeout={400}
              onEnter={this.onEnter}
              onExit={this.onExit}
            >
              <Switch location={location}>
                <Route exact path="/" component={CardBegin} ref={this.myRef} />
                <Route path="/step-1" component={CardStepOne} />
              </Switch>
            </Transition>
          </TransitionGroup>
        )}
      />
    );
  }

 

1. use react-transition-group (http://reactcommunity.org/react-transition-group)

2. use guide from react-router documentation Here

 

In total. Have different keys for two transitions but that I can implement gsap animation from ./animathion.js I need create a ref to DOM node, because my function has two parameters (DOMnode, callback) 

show(target, cb) {
    return TweenMax.from(target, duration, {
      opacity: 0,
      height: 0,
      onComplete() {
        cb();
      },
      ease: Elastic.easeOut.config(0.25, 1)
    });
  },

 

How can I pass there DOMnode for implementation animation?

onEnter = () => {
    animation.show(DOMnode, cb);
  };

Many thanks.

Link to comment
Share on other sites

@n00banim8r you're welcome. Your code looks quite fine and the filter transitions are smooth, good job.

 

As you mention the React GSAP enhancer is quite outdated and I don't see (or heard of)  any intentions in getting it up-to-date with the latest versions of GSAP and React, so I wouldn't recommend it's use. Also as you already saw is not needed when you use Transition Group.

 

So my advice is that when you're animating components being mounted or unmounted, stick with GSAP and React Transition Group and everything should work ok. Also RTG has very good docs for it's API which is very simple actually so is a great tool to use with GSAP.

 

Happy Tweening!!!

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

@mdvoy Is not necessary to go through all that code to get the dom node from react, specially if you're using React Transition Group (RTG).

 

The onEnter, onExit and addEndListener events from RTG already give you access to the DOM node you're animating, which is the one wrapped byt the <Transition> tag. Check them in their API docs here.

 

All you have to do is call a function in one of those methods to trigger an animation when a route changes:

 

<Transition
	key={props.location.pathname}
	timeout={500}
	mountOnEnter={true}
	unmountOnExit={true}
	onEnter={node => {
		// set the position and properties of the entering element
		TweenLite.set(node, {
		  position: "fixed",
		  x: 100,
		  autoAlpha: 0,
		  width: targetWidth
		});
		// animate in the element
		TweenLite.to(node, 0.5, {
		  autoAlpha: 1,
		  x: 0,
		  onComplete: completeCall,
		  onCompleteParams: [node]
		});
	}}

	onExit={node => {
		// animate out the element
		TweenLite.to(node, 0.5, {
		  position: "fixed",
		  opacity: 0,
		  x: -100
		});
	}} // on exit end
>

 

As you can see is not that hard. You can check RTG docs and see all the options you have to work with.

 

Also take a look at this discussion in RTG GitHub repo, regarding page transitions with React Router:

 

https://github.com/reactjs/react-transition-group/issues/136

 

Take a good look at this sample by Matija Marohnić (who is one of the main maintainers of RTG) which is quite simple an works great:

 

https://github.com/reactjs/react-transition-group/issues/136#issuecomment-406626669

 

As personal advice try to avoid, specially if you're not very experienced, the code from React Router's samples. It doesn't have any comments so is not that easy to follow and understand.

 

Finally if you can create a live reduced sample in codesandbox or stackblitz, in order to get a better look at what you're doing, it would be great.

 

Happy Tweening!!!

  • Like 4
Link to comment
Share on other sites

  • 10 months later...
  • 4 weeks later...

@SENPAI & @xraymutation,

 

This is trickier than one can expect because instead of using RTG to animate the transition between route components, the idea is to animate in a full page overlay, change the rendered component based on the new route and then animate the overlay out. Normally in RTG you pass the component that matches the target route which is animated.

 

One solution I can think of is to play with the entering and exiting API's from RTG in order to animate just the overlay and produce the route change using a set instance in the addEndListener in order to have everything working with GSAP's timing. Of course this is easier said than done. Hopefully during the weekend I'll have some time to check this, but I can't make any promises since I'm extremely tight right now time-wise.

 

An alternative is to create an animation in the overlay and use react router's API to make the route changes using GSAP callbacks. The trade-off in this approach is that you loose the capacity of animating the route changes using the browser's forward and back buttons, in those cases the routes change without any animation.

 

Let me know which approach you prefer and perhaps we can work out something together.

 

Happy Tweening!!!

  • Like 4
Link to comment
Share on other sites

  • 1 year later...

Hey @Rodrigo

I am currently trying to animate the page transition for a next.js project. Did you manage to implement this with RTG? That would be super helpful.

Especially the part where you say:

Quote

produce the route change using a set instance in the addEndListener

 

Link to comment
Share on other sites

Hey @trompx,

 

What exactly are you trying to achieve? Next works a bit different than a regular React app, since routing is achieved by using their folder-structure system.

 

I'd suggest to listen to changes in a router instance (which is actually provided by Next) and keep track of the router path in order to decide what to do.

 

I know is a bit cryptic but I'm not really that familiar with Next (I've done a couple of things most never page transitions), so I don't have any real sample to show, so I'd try to explain a bit.

 

For using RTG you can use this approach:

import { useEffect, useState } from "react";
import { useRouter } from "next/router";

const myPath = "/my-path"; // path that shows this component

const Component = () => (
  const router = useRouter();
  const [show, setShow] = useState(false);
  useEffect(() => {
    router && setShow(router.pathname === myPath);
  }, [router]);
  <Transition in={show} addEndListener={/* your animation code here */}>
    <div>I'm a fade Transition!</div>
  </Transition>
 );

If you're using an overlay element then you can remove RTG and just use the same approach (check for updates in the router instance) to animate in and out the overlay element using a similar code as the one above.

 

I know that @elegantseagulls has some experience with Next and probably He has dealt with transitions.

 

Happy Tweening!!!

  • Like 2
Link to comment
Share on other sites

Thanks a lot @Rodrigo! I really appreciate your help :)

 

What I am trying to achieve is to have on the _app.js file a sort of interface that will expose general events (when I say events it may be either real events through an event emitter or just setting a state in the context api for instance -> just a way to communicate a state change between components) that I can extend on specific pages if I need.

 

For instance _app.js would set a default transition from one page to another (say opacity 0->1 for entering page, 1->0 for exiting page or just the fadeIn/Out or SlideIn/Out of an overlay).

Then for some pages, I would like to override and/or extend the default transition to animate page specific elements to enhance the overall effect.

Thus I would need some events like:

- onAnimateIn : which then triggers a doneAnimateIn

- onAnimateOut : which then triggers a doneAnimateOut which would inform RTG/next js that the component can be unmounted

Those events can be called in different order or simultanously.

Say we are on home, if I go to about page, I want the default transition (with an overlay). In that case it would be a OutThenIn type of animation.

If I go to a project page, I would like to animate the project slider on the home page nicely and transition in the project page like if there was no interruption. In that case it would be a OutAndIn type of animation.

 

I don't know how to design the TransitionController loaded in _app.js to handle this.

In your solution, it seems the component is not aware of which is the other page having to transition In/Out so it should be done in a parent container I guess.

Would also love to hear if you already had to deal with that kind of stuff and how you solved this @elegantseagulls 

 

 

 

 

 

Link to comment
Share on other sites

Hey,

 

Well that is a very complete description, no doubt ;), but is beyond what I can spare in terms of time.

 

This is all I can mustard right now, but it should help you get started:

 

https://codesandbox.io/s/gsap-next-page-transitions-n2ozd

 

The approach is quite simple, just generate a Layout component and handle the animations there. I stumbled with the fact that I can't update the in property without creating some state management that would be a bit convoluted (using either context or redux) so instead of using addEndListener I ended up using the onEnter and onExit callbacks to create the animations. Keep in mind that with this approach the timeout prop passed to the <Transition> component has to be equal to the duration of the GSAP instance (the timeout is in milliseconds and the GSAP duration is in second, don't forget!). Also that would give you the flexibility to create and change the animation based on the path prop passed to the <Layout> component.

 

Happy Tweening!!!

  • Like 1
Link to comment
Share on other sites

Hi @trompx ,

I've a fair bit if experience with this, but there's def a lot of gotchas along with it (this may warrant an article on the topic, which I need to find time to write). That said:

To get started, I generally make a PageTransition Component and pass the route, timeout, (and page Y offset, depending on needs) in as a prop and have conditionals based on these props for animation actions, etc.
 

  • Like 2
Link to comment
Share on other sites

Here's a sampler pack from my old site:

https://github.com/agm1984/adammackintosh-dot-net-ssr/blob/master/src/route_animations.js
https://github.com/agm1984/adammackintosh-dot-net-ssr/blob/master/src/Routes.js

Code looks a little savage, but could be useful for scavenging useful parts.

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...