<div id="app"></div>
body {
font-family: Helvetica, Arial, sans-serif;
background: #ECF0F1;
color: #555;
}
.Wrapper {
padding: 30px;
max-width: 800px;
overflow-x: hidden;
margin: 0 auto;
position: relative;
}
.Dummy {
padding: 25px;
}
p {
line-height: 1.4em;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
}
.TransitionHeight {
border: 1px solid #DDD;
border-radius: 5px;
background: #fff;
}
.Button {
margin: 0 0 15px;
border: 1px solid #ddd;
background: #fff;
font-size: 16px;
border-radius: 50px;
padding: 10px 18px;
transition: all 300ms;
outline: none;
&:hover {
color: #2980B9;
border-color: lighten(#2980B9, 30);
cursor: pointer;
}
}
View Compiled
const ANIMATION_DURATION = 1000;
class TransitionHeight extends React.Component {
constructor() {
super();
this.state = {
previousChildren: null,
height: 'auto',
previousChildrenOpacity: 0,
childrenOpacity: 1,
animate: false,
};
}
componentWillReceiveProps(nextProps) {
const {
children,
} = this.props;
if (children !== nextProps.children) {
// Set animation initial state
this.setState({
previousChildren: children,
height: this.mainElement.offsetHeight,
previousChildrenOpacity: 1,
childrenOpacity: 0,
});
cancelAnimationFrame(this.animationStartRequestID);
// 50ms delay to let react render init state
this.animationStartRequestID = requestAnimationFrame(() => {
// Set animation end state
this.setState({
previousChildrenOpacity: 0,
childrenOpacity: 1,
height: this.contentElement.offsetHeight,
animate: true,
});
clearTimeout(this.animationEndTimeoutID);
// When animation is finished, reset state
this.animationEndTimeoutID = setTimeout(() => {
this.setState({
previousChildren: null,
height: 'auto',
animate: false,
});
}, ANIMATION_DURATION);
});
}
}
render() {
const {
children,
} = this.props;
const {
previousChildren,
height,
previousChildrenOpacity,
childrenOpacity,
animate,
} = this.state;
const mainElementStyle = {
position: 'relative',
transition: animate ? `all ${ ANIMATION_DURATION }ms` : null,
height,
overflow: 'hidden',
};
const childrenStyle = {
transition: animate ? `all ${ ANIMATION_DURATION }ms` : null,
opacity: childrenOpacity,
};
const previousChildrenStyle = {
position: 'absolute',
width: '100%',
transition: animate ? `all ${ ANIMATION_DURATION }ms` : null,
top: 0,
left: 0,
opacity: previousChildrenOpacity,
};
return (
<div
style={ mainElementStyle }
ref={ el => this.mainElement = el }
className='TransitionHeight'
>
<div
style={ childrenStyle }
ref={ el => this.contentElement = el }
>
{ children }
</div>
<div style={ previousChildrenStyle }>
{ previousChildren }
</div>
</div>
);
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
swap: false,
};
}
render() {
return (
<div className='Wrapper'>
<button
className='Button'
onClick={ () => this.setState({ swap: !this.state.swap }) }
>
Swap
</button>
<TransitionHeight>
{ this.state.swap ?
<div className='Dummy'>
<p>Short one</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer efficitur, augue in euismod pretium, erat metus bibendum nisi,
at dictum lorem sapien vitae risus. Etiam et aliquam nisl.
Donec placerat nibh ut tellus elementum, quis pulvinar orci dictum.
Quisque convallis nisl in eros consequat, ut aliquam ante suscipit.
Sed pharetra eros maximus suscipit viverra. Fusce posuere lorem in neque porttitor,
eget dignissim erat pharetra. Proin fermentum orci purus.
Nam ultricies sodales imperdiet. Mauris at nibh quis ex fermentum blandit.
Integer sit amet sagittis sapien.
</p>
</div> :
<div className='Dummy'>
<p>Longer one</p>
<p>
Donec placerat nibh ut tellus elementum, quis pulvinar orci dictum.
Quisque convallis nisl in eros consequat, ut aliquam ante suscipit.
Sed pharetra eros maximus suscipit viverra. Fusce posuere lorem in neque porttitor,
eget dignissim erat pharetra. Proin fermentum orci purus.
Nam ultricies sodales imperdiet. Mauris at nibh quis ex fermentum blandit.
Integer sit amet sagittis sapien.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer efficitur, augue in euismod pretium, erat metus bibendum nisi,
at dictum lorem sapien vitae risus. Etiam et aliquam nisl.
Donec placerat nibh ut tellus elementum, quis pulvinar orci dictum.
Quisque convallis nisl in eros consequat, ut aliquam ante suscipit.
Sed pharetra eros maximus suscipit viverra. Fusce posuere lorem in neque porttitor,
eget dignissim erat pharetra. Proin fermentum orci purus.
Nam ultricies sodales imperdiet. Mauris at nibh quis ex fermentum blandit.
Integer sit amet sagittis sapien.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Integer efficitur, augue in euismod pretium, erat metus bibendum nisi,
at dictum lorem sapien vitae risus. Etiam et aliquam nisl.
Donec placerat nibh ut tellus elementum, quis pulvinar orci dictum.
Quisque convallis nisl in eros consequat, ut aliquam ante suscipit.
Sed pharetra eros maximus suscipit viverra. Fusce posuere lorem in neque porttitor,
eget dignissim erat pharetra. Proin fermentum orci purus.
Nam ultricies sodales imperdiet. Mauris at nibh quis ex fermentum blandit.
Integer sit amet sagittis sapien.
</p>
</div> }
</TransitionHeight>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('app'));
View Compiled
This Pen doesn't use any external CSS resources.