React - animate mount and unmount of a single component

前端 未结 16 1308
南笙
南笙 2020-12-22 16:34

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

相关标签:
16条回答
  • 2020-12-22 16:53

    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.

    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>

    0 讨论(0)
  • 2020-12-22 16:54

    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.

    0 讨论(0)
  • 2020-12-22 16:58

    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>
    
    0 讨论(0)
  • 2020-12-22 16:59

    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.

    A stage cycle might look like this

    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:)

    0 讨论(0)
  • 2020-12-22 16:59

    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>
        );
      }
    }

    0 讨论(0)
  • 2020-12-22 17:01

    I know there are a lot of answers here, but I still did not find one that suits my needs. I want:

    • Functional components
    • A solution that'll allow my components to easily fade in/out when they're mounted/unmounted.

    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>.

    0 讨论(0)
提交回复
热议问题