Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React: Animating a page switch

I was wondering if anyone could provide some insight about how to achieve an animated component/page switch with React.js.

What i'd like to achieve is a component/page transition like the one on http://www.semplicelabs.com/ - a header that transitions opacity and margin-top and a content that transitions opacity.

When a new component/page is shown, the currently shown component/page should transition out before the new component/page transitioning in. What i have so far is a LayoutComponent that renders a page component into a CSSTransitionGroup:

import React from 'react/addons';

export default class LayoutComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { page: '' };
  },

  setPage(page) {
    this.setState({ page });
  }

  render() {
    return (
      <React.addons.CSSTransitionGroup transitionName='page'>
        {this.state.page}
      </React.addons.CSSTransitionGroup>
    );
  }
}

I also have two page components, FirstPage and SecondPage.

import React from 'react';

export default class FirstPage extends React.Component {
  render() {
    return (
      <div>
        <header className='header'>
          <div className='container'>
            First Header
          </div>
        </header>
        <div className='content'>
          <div className='container'>
            First Content
          </div>
        </div>
      </div>
    );
  }
}

The only differences between SecondPage code and FirstPage code are the contents in the .container divs and the classes name.

What i then tried to do is add a leave transition with .8s duration and an enter transition with both .8s duration and delay. The main problem i see is that the new page component is mounted before the leave transition of the old one has finished. This is my current CSS:

.page-enter {
  background-color: #f2f2f2;
  transition: background-color .8s linear .8s;
}

.page-enter-active {
  background-color: #f2f1f1;
}

.page-leave {
  background-color: #f2f1f1;
  transition: background-color .8s linear;
}

.page-leave-active {
  background-color: #f2f2f2;
}

.page-enter .header {
  margin-top: -80px;
  opacity: 0;
  transition: opacity .8s ease-in-out .8s;
  transition: margin-top .8s ease-in-out .8s;
}

.page-enter-active .header {
  margin-top: 0;
  opacity: 1;
}

.page-leave .header {
  margin-top: 0;
  opacity: 1;
  transition: opacity .8s ease-in-out;
  transition: margin-top .8s ease-in-out;
}

.page-leave-active .header {
  margin-top: -80px;
  opacity: 0;
}

.page-enter .content {
  opacity: 0;
  transition: opacity .8s ease-in-out .8s;
}

.page-enter-active .content {
  opacity: 1;
}

.page-leave .content {
  opacity: 1;
  transition: opacity .8s ease-in-out;
}

.page-leave-active .content {
  opacity: 0;
}

I know i could animate the initial mounting with transitionAppear={true} - but that doesn't help with the problem of the new component being mounted before the old one is transitioned out.

This is a problem I have struggled with for a few days and I can't find a solution for.

Some code to play around with: https://jsfiddle.net/gonsfx/xva2g6oo/

like image 254
Codepunkt Avatar asked Nov 09 '22 10:11

Codepunkt


1 Answers

i have added a state to the pages to save a opacity (for the animation) and set it to 1 (initial = 0) when the component did mount with a small delay. So i added two functions to each page.

componentDidMount() {
    setTimeout(() => {
        this.setState({
            opacity: 1
        });
    }, 20);
}

getStyle() {
    return { opacity: this.state.opacity };
}

Then I added a constructor to make initial stuff....

constructor(props) {
    super(props);
    this.state = { opacity: 0 };
    this.getStyle = this.getStyle.bind(this);
    this.componentDidMount = this.componentDidMount.bind(this);
}

Here is a DEMO.

The components will appear immediately on the page, so i think that it isn't possible to make a fade out animation (At least, I did not found a solution for that).

like image 151
marcel Avatar answered Nov 14 '22 23:11

marcel