Jump to content
mb2233

Animated Page Transitions in React using GSAP

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! 

 

Share this post


Link to post
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

Share this post


Link to post
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!  

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
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

Share this post


Link to post
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.

Share this post


Link to post
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

Share this post


Link to post
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

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.