Something this simple should be easily accomplished, yet I\'m pulling my hair out over how complicated it is.
All I want to do is animate the mounting & unmounti
This is a bit lengthy but I've used all the native events and methods to achieve this animation. No ReactCSSTransitionGroup
, ReactTransitionGroup
and etc.
Things I've used
onTransitionEnd
eventHow this works
mounted
) and with default style(opacity: 0
)componentDidMount
(componentWillReceiveProps
for further updates)to change the style (opacity: 1
) with a timeout(to make it async).opacity: 0
), onTransitionEnd
, remove unmount the element from the DOM.Continue the cycle.
Go through the code, you'll understand. If any clarification is needed, please leave a comment.
Hope this helps.
class App extends React.Component{
constructor(props) {
super(props)
this.transitionEnd = this.transitionEnd.bind(this)
this.mountStyle = this.mountStyle.bind(this)
this.unMountStyle = this.unMountStyle.bind(this)
this.state ={ //base css
show: true,
style :{
fontSize: 60,
opacity: 0,
transition: 'all 2s ease',
}
}
}
componentWillReceiveProps(newProps) { // check for the mounted props
if(!newProps.mounted)
return this.unMountStyle() // call outro animation when mounted prop is false
this.setState({ // remount the node when the mounted prop is true
show: true
})
setTimeout(this.mountStyle, 10) // call the into animation
}
unMountStyle() { // css for unmount animation
this.setState({
style: {
fontSize: 60,
opacity: 0,
transition: 'all 1s ease',
}
})
}
mountStyle() { // css for mount animation
this.setState({
style: {
fontSize: 60,
opacity: 1,
transition: 'all 1s ease',
}
})
}
componentDidMount(){
setTimeout(this.mountStyle, 10) // call the into animation
}
transitionEnd(){
if(!this.props.mounted){ // remove the node on transition end when the mounted prop is false
this.setState({
show: false
})
}
}
render() {
return this.state.show && <h1 style={this.state.style} onTransitionEnd={this.transitionEnd}>Hello</h1>
}
}
class Parent extends React.Component{
constructor(props){
super(props)
this.buttonClick = this.buttonClick.bind(this)
this.state = {
showChild: true,
}
}
buttonClick(){
this.setState({
showChild: !this.state.showChild
})
}
render(){
return <div>
<App onTransitionEnd={this.transitionEnd} mounted={this.state.showChild}/>
<button onClick={this.buttonClick}>{this.state.showChild ? 'Unmount': 'Mount'}</button>
</div>
}
}
ReactDOM.render(<Parent />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-with-addons.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
You could always use React lifecycle methods but react-transition-group is by far the most convenient library for animations I have come across, Whether you are using styled-components
or plain css. It is especially useful when you want to track the mounting and unmounting of your component and render animations accordingly.
Use Transition
with styled-components and CSSTransition
when you are using plain css classnames.
Using the knowledge gained from Pranesh's answer, I came up with an alternate solution that's configurable and reusable:
const AnimatedMount = ({ unmountedStyle, mountedStyle }) => {
return (Wrapped) => class extends Component {
constructor(props) {
super(props);
this.state = {
style: unmountedStyle,
};
}
componentWillEnter(callback) {
this.onTransitionEnd = callback;
setTimeout(() => {
this.setState({
style: mountedStyle,
});
}, 20);
}
componentWillLeave(callback) {
this.onTransitionEnd = callback;
this.setState({
style: unmountedStyle,
});
}
render() {
return <div
style={this.state.style}
onTransitionEnd={this.onTransitionEnd}
>
<Wrapped { ...this.props } />
</div>
}
}
};
Usage:
import React, { PureComponent } from 'react';
class Thing extends PureComponent {
render() {
return <div>
Test!
</div>
}
}
export default AnimatedMount({
unmountedStyle: {
opacity: 0,
transform: 'translate3d(-100px, 0, 0)',
transition: 'opacity 250ms ease-out, transform 250ms ease-out',
},
mountedStyle: {
opacity: 1,
transform: 'translate3d(0, 0, 0)',
transition: 'opacity 1.5s ease-out, transform 1.5s ease-out',
},
})(Thing);
And finally, in another component's render
method:
return <div>
<ReactTransitionGroup>
<Thing />
</ReactTransitionGroup>
</div>
I countered this problem during my work, and simple as it seemed, it is really not in React. In a normal scenario where you render something like:
this.state.show ? {childen} : null;
as this.state.show
changes the children are mounted/unmounted right away.
One approach I took is creating a wrapper component Animate
and use it like
<Animate show={this.state.show}>
{childen}
</Animate>
now as this.state.show
changes, we can perceive prop changes with getDerivedStateFromProps(componentWillReceiveProps)
and create intermediate render stages to perform animations.
We start with Static Stage when the children is mounted or unmounted.
Once we detect the show
flag changes, we enter Prep Stage where we calculate necessary properties like height
and width
from ReactDOM.findDOMNode.getBoundingClientRect()
.
Then entering Animate State we can use css transition to change height, width and opacity from 0 to the calculated values (or to 0 if unmounting).
At the end of transition, we use onTransitionEnd
api to change back to
Static
stage.
There are much more details to how the stages transfer smoothly but this could be overall idea:)
If anyone interested, I created a React library https://github.com/MingruiZhang/react-animate-mount to share my solution. Any feedback welcome:)
I was also in dire need of single component Animation . I tired using React Motion but i was pulling my hairs for such a trivial issue.. (i thing). After some googling i came across this post on their git repo . Hope it helps someone..
Referenced From & also the credit. This works for me as of now. My use case was a modal to animate and unmount in case of load and unload.
class Example extends React.Component {
constructor() {
super();
this.toggle = this.toggle.bind(this);
this.onRest = this.onRest.bind(this);
this.state = {
open: true,
animating: false,
};
}
toggle() {
this.setState({
open: !this.state.open,
animating: true,
});
}
onRest() {
this.setState({ animating: false });
}
render() {
const { open, animating } = this.state;
return (
<div>
<button onClick={this.toggle}>
Toggle
</button>
{(open || animating) && (
<Motion
defaultStyle={open ? { opacity: 0 } : { opacity: 1 }}
style={open ? { opacity: spring(1) } : { opacity: spring(0) }}
onRest={this.onRest}
>
{(style => (
<div className="box" style={style} />
))}
</Motion>
)}
</div>
);
}
}
I know there are a lot of answers here, but I still did not find one that suits my needs. I want:
After many hours of fiddling, I have a solution that works I'd say 90%. I've written the limitation in a comment block in the code below. I'd still love a better solution, but this is the best I've found, including the other solutions here.
const TIMEOUT_DURATION = 80 // Just looked like best balance of silky smooth and stop delaying me.
// Wrap this around any views and they'll fade in and out when mounting /
// unmounting. I tried using <ReactCSSTransitionGroup> and <Transition> but I
// could not get them to work. There is one major limitation to this approach:
// If a component that's mounted inside of <Fade> has direct prop changes,
// <Fade> will think that it's a new component and unmount/mount it. This
// means the inner component will fade out and fade in, and things like cursor
// position in forms will be reset. The solution to this is to abstract <Fade>
// into a wrapper component.
const Fade: React.FC<{}> = ({ children }) => {
const [ className, setClassName ] = useState('fade')
const [ newChildren, setNewChildren ] = useState(children)
const effectDependency = Array.isArray(children) ? children : [children]
useEffect(() => {
setClassName('fade')
const timerId = setTimeout(() => {
setClassName('fade show')
setNewChildren(children)
}, TIMEOUT_DURATION)
return () => {
clearTimeout(timerId)
}
}, effectDependency)
return <Container fluid className={className + ' p-0'}>{newChildren}</Container>
}
If you have a component you want to fade in/out, wrap it in <Fade>
Ex. <Fade><MyComponent/><Fade>
.
Note that this uses react-bootstrap
for the class names and for <Container/>
, but both could be easily replaced with custom CSS and a regular old <div>
.