Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React smooth transition between different states in a component

I have a simple component like this:

var component = React.createClass({
  render: function(){
      if (this.props.isCollapsed){
         return this.renderCollapsed();
      }
      return this.renderActive()
  },
  renderActive: function(){
    return (
      <div>
      ...
      </div>
    );
  },
  renderCollapsed: function(){
    return (
      <div>
      ...
      </div>
    );
  },
});

Basically, when the property changes, the component will either show active state or collapse state.

What I am thinking is, when the property change happens, i.e. active->collapse, or the other way around, I want the old view "shrink" or "expand" smoothly to show the new view. For example, if it is active -> collapse, I want the active UI to shrink to the size of collapse UI, and show it smoothly.

I am not sure how to achieve this effect. Please share some ideas. Thanks!

like image 281
Allan Jiang Avatar asked Dec 08 '16 02:12

Allan Jiang


4 Answers

Here is a minimal working example:

const collapsible = ({active, toggle}) =>
<div>
  <button type="button" onClick={toggle}>Toggle</button>
  <div className={'collapsible' + (active? ' active': '')}>
    text
  </div>
</div>


const component = React.createClass({
  getInitialState() {
    return {active: false}
  },
  toggle() {
    this.setState({active: !this.state.active})
  },
  render() {
    return collapsible({active: this.state.active, toggle: this.toggle})
  }
})
ReactDOM.render(React.createElement(component), document.querySelector('#root'))
.collapsible {
  height: 1.5rem;
  transition: height 0.25s linear;
  background: #333;
  border-radius: 0.25rem
}
.collapsible.active {
  height: 7rem
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div id="root"></div>

The view can be "shrink" or "expand" smoothly by CSS transition, which is triggered by changing CSS properties.

To control CSS properties with React, we can reflect state changes to property values or className in render().

In this example, .active class affects height value, and is controlled by state.active. The class is toggled by React in response to state changes, and triggers CSS transition.

For smoother transitions, See this article.

like image 187
DarkKnight Avatar answered Nov 15 '22 20:11

DarkKnight


As you want to render two different component for each of active and collapsed, wrap them in a div that controls the height with the help of CSS.

render: function(){
var cls = this.props.isCollapsed() ? 'collapsed' : 'expanded';
return(
  <div className={cls + ' wrapper'}>
    {
      this.props.isCollapsed() ?
       this.renderCollapsed() :
       this.renderActive()
    }
  </div>
);
}

and in your CSS:

.wrapper{
  transition: transform .5s linear;
}

.expanded{  
  height: 200px;
}

.collapsed{
  height: 20px;
}
like image 43
eskawl Avatar answered Nov 15 '22 20:11

eskawl


One more approach to this situation might be changing state after animation completes. The benefits of it is that you can apply not only transitions but whatever actions you want (js animations, smil, etc ..), main thing is not to forget to call an end callback;)

Here is working example CodePen

And here is the code example:

const runTransition = (node, {property = 'opacity', from, to, duration = 600, post = ''}, end) => {
  const dif = to - from;
  const start = Date.now();

  const animate = ()=>{
    const step = Date.now() - start;
    if (step >= duration) {
      node.style[property] = to + post;
      return typeof end == 'function' && end();
    }

    const val =from + (dif * (step/duration));
    node.style[property] =  val + post;
    requestAnimationFrame(animate);
  }

  requestAnimationFrame(animate);

}

class Comp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isCollapsed: false
    }

    this.onclick = (e)=>{
       this.hide(e.currentTarget,()=>{
         this.setState({isCollapsed: !this.state.isCollapsed})
       });
     };

    this.refF = (n)=>{
       n && this.show(n);
     };
  }

  render() {
    if (this.state.isCollapsed){
         return this.renderCollapsed();
      }
      return this.renderActive()
  }

  renderCollapsed() {
    return (
      <div 
        key='b'
        style={{opacity: 0}}
        ref={this.refF}
        className={`b`}
        onClick={this.onclick}>
          <h2>I'm Collapsed</h2>
      </div>
      )
  }

  renderActive() {
    return (
      <div 
        key='a'
        style={{opacity: 0}}
        ref={this.refF}
        className={`a`}
        onClick={this.onclick}>
          <h2>I'm Active</h2>
      </div>
      )
  }

  show(node, cb)  {
    runTransition(node, {from: 0, to: 1}, cb);
  }

  hide(node, cb) {
    runTransition(node, {from: 1, to: 0}, cb);
  }

}

ReactDOM.render(<Comp />, document.getElementById('content'));

And for sure, for this approach to work your only opportunity is to relay on state, instead of props of Component, which you can always set in componentWillReceiveProps method if you have to deal with them.

Updated

Codepen link updated with more clear example which is showing benefits of this approach. Transition changed to javascript animation, without relying on transitionend event.

like image 20
Leonid Lazaryev Avatar answered Nov 15 '22 19:11

Leonid Lazaryev


The standard way is to use CSSTransitionGroup from react-transition-group, which is quite easy. Wrap the component with the CSSTransitionGroup and set timeouts on enter and leave, like this:

<CSSTransitionGroup
      transitionName="example"
      transitionEnterTimeout={500}
      transitionLeaveTimeout={300}>
      {items}
</CSSTransitionGroup>

From the v1-stable docs:

"In this component, when a new item is added to CSSTransitionGroup it will get the example-enter CSS class and the example-enter-active CSS class added in the next tick."

Add styling for the CSS classes to get the correct animation.

There's also pretty good explanation in React docs, check it out.

There are third-party components for animation as well.

like image 43
gaperton Avatar answered Nov 15 '22 19:11

gaperton