Reputation: 11341
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!
Upvotes: 15
Views: 34094
Reputation: 3826
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.
Upvotes: 7
Reputation: 326
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.
Codepen link updated with more clear example which is showing benefits of this approach. Transition changed to javascript animation, without relying on transitionend event.
Upvotes: 3
Reputation: 1263
Here you have a Toggle react component using the Velocity-React library, which is great for giving animations to transitions in React uis:
import React, { Component } from 'react';
import { VelocityTransitionGroup } from 'velocity-react';
export default class ToggleContainer extends Component {
constructor () {
super();
this.renderContent = this.renderContent.bind(this);
}
renderContent () {
if (this.props.show) {
return (
<div className="toggle-container-container">
{this.props.children}
</div>
);
}
return null
}
render () {
return (
<div>
<h2 className="toggle-container-title" onClick={this.props.toggle}>{this.props.title}</h2>
<VelocityTransitionGroup component="div" enter="slideDown" leave="slideUp">
{this.renderContent()}
</VelocityTransitionGroup>
</div>
);
}
};
ToggleContainer.propTypes = {
show: React.PropTypes.bool,
title: React.PropTypes.string.isRequired,
toggle: React.PropTypes.func.isRequired,
};
Hope it helps!
Upvotes: 0
Reputation: 637
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;
}
Upvotes: 2
Reputation: 7593
Rather than conditionally render two different end states of a component, you could instead
toggle the class on the same component. You could have active and collapsed classes as follows:
For example:
.active{
-webkit-transition: -webkit-transform .5s linear; // transition of
// 0.5 of a second
height: 200px;
}
.collapsed{
height: 0px;
}
Check out this resource for examples
Upvotes: 6
Reputation: 5944
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.
Upvotes: 11
Reputation: 908
You can add a class that describes the active state ie. .active
and toggle that class when switching states.
The css should look something like this:
.your-component-name{
// inactive css styling here
}
.your-component-name.active {
// active css styling here
}
Upvotes: 1