5

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/

Codepunkt
  • 532
  • 4
  • 15

2 Answers2

1

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).

marcel
  • 2,967
  • 1
  • 16
  • 25
  • That doesn't fade out the old page before showing the new one. Also, it's completely bypasses `CSSTransitionGroup`. Thanks for your effort, though. – Codepunkt Jul 01 '15 at 13:04
0

OK. After wading through a lot of latest as well as outdated docs, I managed to work this out. Here is the solution

Basically, react-router concerns itself only with the animation of parent container. But that means that using ReactTransitionGroup, we could hook into componentWillLeave method in the exiting component, where we can make individual children manipulate. The children components may also sport ReactCSSTransitionGroup in order to facilitate appear transition.

So you may not be able to use only CSSTransitionGroup, but a mix of CSSTransitionGroup and TransitionGroup.

Thus, changing a page would automatically animate the children through the hook, while entering a page would use the CSSTransitionGroup for individual animations.

The setTimeout in the solution may seem like a hack, but that's all I have been able to do for now.

bhootjb
  • 1,501
  • 1
  • 21
  • 33