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 & unmounting of a React component, that's it. Here's what I've tried so far and why each solution won't work:
ReactCSSTransitionGroup
- I'm not using CSS classes at all, it's all JS styles, so this won't work.ReactTransitionGroup
- This lower level API is great, but it requires you to use a callback when the animation is complete, so just using CSS transitions won't work here. There are always animation libraries, which leads to the next point:- GreenSock - The licensing is too restrictive for business use IMO.
- React Motion - This seems great, but
TransitionMotion
is extremely confusing and overly complicated for what I need. - Of course I can just do trickery like Material UI does, where the elements are rendered but remain hidden (
left: -10000px
) but I'd rather not go that route. I consider it hacky, and I want my components to unmount so they clean up and are not cluttering up the DOM.
I want something that's easy to implement. On mount, animate a set of styles; on unmount, animate the same (or another) set of styles. Done. It also has to be high performance on multiple platforms.
I've hit a brick wall here. If I'm missing something and there's an easy way to do this, let me know.
ベストアンサー1
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
- React lifecycle methods
onTransitionEnd
event
How this works
- Mount the element based on the mount prop passed(
mounted
) and with default style(opacity: 0
) - After mount or update, use
componentDidMount
(componentWillReceiveProps
for further updates)to change the style (opacity: 1
) with a timeout(to make it async). - During unmount, pass a prop to the component to identify unmount, change the style again(
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.
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>