【问题标题】:React - animate mount and unmount of a single componentReact - 动画安装和卸载单个组件
【发布时间】:2017-02-25 03:07:58
【问题描述】:

这么简单的事情应该很容易完成,但我对它的复杂程度感到震惊。

我要做的就是为 React 组件的安装和卸载设置动画,仅此而已。以下是我到目前为止尝试过的方法以及每种解决方案都不起作用的原因:

  1. ReactCSSTransitionGroup - 我根本没有使用 CSS 类,都是 JS 样式,所以这行不通。
  2. ReactTransitionGroup - 这个较低级别的 API 很棒,但是它需要您在动画完成时使用回调,所以在这里仅使用 CSS 过渡将不起作用。总有动画库,这就引出了下一点:
  3. GreenSock - 许可对 IMO 的商业用途过于严格。
  4. React Motion - 这看起来很棒,但 TransitionMotion 对我的需要来说非常混乱和过于复杂。
  5. 当然,我可以像 Material UI 那样做一些诡计,其中元素被渲染但保持隐藏 (left: -10000px) 但我宁愿不走那条路。我认为它很老套,我希望卸载我的组件,以便它们清理并且不会弄乱 DOM。

我想要容易实现的东西。在安装时,为一组样式设置动画;在卸载时,为相同(或另一组)样式设置动画。完毕。它还必须在多个平台上具有高性能。

我在这里碰壁了。如果我遗漏了什么并且有一种简单的方法可以做到这一点,请告诉我。

【问题讨论】:

  • 我们这里说的是什么动画?
  • 只是一些简单的东西,比如 CSS 不透明度淡入和 transform: scale
  • 第 1 点和第 2 点让我很困惑。你使用什么样的动画? JS 过渡还是 CSS 过渡?
  • 不要将 CSS 样式/类(例如.thing { color: #fff; })与 JS 样式(const styles = { thing: { color: '#fff' } })混淆)
  • 但问题是,当您尝试使用 javascript 更改样式时,您实际上是在替换不会提供任何过渡的元素的样式。

标签: animation reactjs css-animations gsap react-motion


【解决方案1】:

如果您正在寻找简单的钩子示例:

import React, { useEffect, useReducer } from "react";
import ReactDOM from "react-dom";

const ANIMATION_TIME = 2 * 1000;

function Component() {
  const [isMounted, toggleMounted] = useReducer((p) => !p, true);
  const [isAnimateAnmount, toggleAnimateUnmount] = useReducer((p) => !p, false);
  const [isVisible, toggleVisible] = useReducer((p) => (p ? 0 : 1), 0);

  useEffect(() => {
    if (isAnimateAnmount) {
      toggleVisible();
      toggleAnimateUnmount();
      setTimeout(() => {
        toggleMounted();
      }, ANIMATION_TIME);
    }
  }, [isAnimateAnmount]);

  useEffect(() => {
    toggleVisible();
  }, [isMounted]);

  return (
    <>
      <button onClick={toggleAnimateUnmount}>toggle</button>
      <div>{isMounted ? "Mounted" : "Unmounted"}</div>
      {isMounted && (
        <div
          style={{
            fontSize: 60,
            opacity: isVisible,
            transition: "all 2s ease"
          }}
        >
          Example
        </div>
      )}
    </>
  );
}

【讨论】:

    【解决方案2】:

    您可以使用React Transition Group 执行此操作。它为您提供 CSS 类,因此您可以在这些 CSS 类中编写动画代码。

    按照这个简单的例子进行

    import {CSSTransition } from 'react-transition-group';//This should be imported
    import './AnimatedText.css';
    
    const AnimatedText = () => {
        const [showText, setShowText] = useState(false); //By default text will be not shown
    
        //Handler to switch states
        const switchHandler = () =>{
            setShowText(!showText);
        };
    
        return (
            //in : pass your state here, it will used by library to toggle. It should be boolean
            //timeout: your amination total time(it should be same as mentioned in css)
            //classNames: give class name of your choice, library will prefix it with it's animation classes
            //unmountOnExit: Component will be unmounted when your state changes to false
            <CSSTransition in={showText} timeout={500} classNames='fade' unmountOnExit={true}>
                <h1>Animated Text</h1>
            </CSSTransition>  
            <button onClick={switchHandler}>Show Text</button>                  
        );
    };
    
    export default AnimatedText;
    

    现在,让我们在 CSS 文件(AnimatedText.css)中编写动画,记住 classNames 属性(在本例中为 fade)

    //fade class should be prefixed
    
    /*****Fade In effect when component is mounted*****/
    //This is when your animation starts
    fade-enter {
      opacity: 0;
    }
    
    //When your animation is active
    .fade-enter.fade-enter-active {
      opacity: 1;
      transition: all 500ms ease-in;
    }
    /*****Fade In effect when component is mounted*****/
    
    
    /*****Fade Out effect when component is unmounted*****/
    .fade-exit {
      opacity: 1;
    }
    .fade-exit-active {
      opacity: 0;
      transition: all 500ms ease-out;
    }
    
    /*****Fade Out effect when component is unmounted*****/
    

    还有一个出现类,可以在你的组件第一次加载时使用。查看文档了解更多详情

    【讨论】:

      【解决方案3】:

      你总是可以使用 React 生命周期方法,但 react-transition-group 是迄今为止我遇到的最方便的动画库,无论你是使用 styled-components 还是纯 css。当您想要跟踪组件的安装和卸载并相应地渲染动画时,它特别有用。 Transition 与 styled-components 一起使用,CSSTransition 在使用纯 css 类名时使用。

      【讨论】:

        【解决方案4】:

        您可以为此使用 React SyntheticEvent

        使用 onAnimationEndonTransitionEnd 之类的事件,您可以做到这一点。

        React 文档:https://reactjs.org/docs/events.html#animation-events

        代码示例:https://dev.to/michalczaplinski/super-easy-react-mount-unmount-animations-with-hooks-4foj

        【讨论】:

          【解决方案5】:

          如果我使用VelocityAnimeJS 库直接为节点设置动画(而不是csssetTimeout),那么我发现我可以设计一个hook 来提供动画状态on 和函数onToggle 启动动画(例如向下滑动、淡入淡出)。

          钩子的作用基本上是打开和关闭动画,然后之后相应地更新on。因此我们可以准确地获取动画的状态。如果不这样做,将回复临时duration

          /**
           * A hook to provide animation status.
           * @class useAnimate
           * @param {object} _                props
           * @param {async} _.animate         Promise to perform animation
           * @param {object} _.node           Dom node to animate
           * @param {bool} _.disabled         Disable animation
           * @returns {useAnimateObject}      Animate status object
           * @example
           *   const { on, onToggle } = useAnimate({
           *    animate: async () => { },
           *    node: node
           *  })
           */
          
          import { useState, useCallback } from 'react'
          
          const useAnimate = ({
            animate, node, disabled,
          }) => {
            const [on, setOn] = useState(false)
          
            const onToggle = useCallback(v => {
              if (disabled) return
              if (v) setOn(true)
              animate({ node, on: v }).finally(() => {
                if (!v) setOn(false)
              })
            }, [animate, node, disabled, effect])
          
            return [on, onToggle]
          }
          
          export default useAnimate
          

          用法如下,

            const ref = useRef()
            const [on, onToggle] = useAnimate({
              animate: animateFunc,
              node: ref.current,
              disabled
            })
            const onClick = () => { onToggle(!on) }
          
            return (
                <div ref={ref}>
                    {on && <YOUROWNCOMPONENT onClick={onClick} /> }
                </div>
            )
          
          

          动画实现可以是,

          import anime from 'animejs'
          
          const animateFunc = (params) => {
            const { node, on } = params
            const height = on ? 233 : 0
            return new Promise(resolve => {
              anime({
                targets: node,
                height,
                complete: () => { resolve() }
              }).play()
            })
          }
          
          

          【讨论】:

            【解决方案6】:

            我知道这里有很多答案,但我仍然没有找到适合我需要的答案。我要:

            • 功能组件
            • 一种解决方案,可让我的组件在安装/卸载时轻松淡入/淡出。

            经过几个小时的摆弄,我有一个可行的解决方案,我会说 90%。我在下面的代码中的注释块中写了限制。我仍然喜欢更好的解决方案,但这是我找到的最好的解决方案,包括此处的其他解决方案。

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

            如果您有想要淡入/淡出的组件,请将其包裹在&lt;Fade&gt; Ex. &lt;Fade&gt;&lt;MyComponent/&gt;&lt;Fade&gt;.

            请注意,这使用 react-bootstrap 作为类名和 &lt;Container/&gt;,但两者都可以轻松地替换为自定义 CSS 和常规的旧 &lt;div&gt;

            【讨论】:

              【解决方案7】:

              Framer motion

              从 npm 安装 framer-motion。

              import { motion, AnimatePresence } from "framer-motion"
              
              export const MyComponent = ({ isVisible }) => (
                <AnimatePresence>
                  {isVisible && (
                    <motion.div
                      initial={{ opacity: 0 }}
                      animate={{ opacity: 1 }}
                      exit={{ opacity: 0 }}
                    />
                  )}
                </AnimatePresence>
              )
              

              【讨论】:

                【解决方案8】:

                这是我使用新的钩子 API(使用 TypeScript)based on this post 来延迟组件的卸载阶段的解决方案:

                function useDelayUnmount(isMounted: boolean, delayTime: number) {
                    const [ shouldRender, setShouldRender ] = useState(false);
                
                    useEffect(() => {
                        let timeoutId: number;
                        if (isMounted && !shouldRender) {
                            setShouldRender(true);
                        }
                        else if(!isMounted && shouldRender) {
                            timeoutId = setTimeout(
                                () => setShouldRender(false), 
                                delayTime
                            );
                        }
                        return () => clearTimeout(timeoutId);
                    }, [isMounted, delayTime, shouldRender]);
                    return shouldRender;
                }
                

                用法:

                const Parent: React.FC = () => {
                    const [ isMounted, setIsMounted ] = useState(true);
                    const shouldRenderChild = useDelayUnmount(isMounted, 500);
                    const mountedStyle = {opacity: 1, transition: "opacity 500ms ease-in"};
                    const unmountedStyle = {opacity: 0, transition: "opacity 500ms ease-in"};
                
                    const handleToggleClicked = () => {
                        setIsMounted(!isMounted);
                    }
                
                    return (
                        <>
                            {shouldRenderChild && 
                                <Child style={isMounted ? mountedStyle : unmountedStyle} />}
                            <button onClick={handleToggleClicked}>Click me!</button>
                        </>
                    );
                }
                

                CodeSandbox链接。

                【讨论】:

                • 优雅的解决方案,如果您添加了一些 cmets 会很棒:)
                • 为什么还要使用 typescrypt 的扩展,因为它在 javascript 的扩展中运行良好?
                • 您的控制台也返回“找不到命名空间 NodeJS 超时”
                • @Webwoman 感谢您的 cmets。我无法使用“NodeJS 超时”重新创建您报告的问题,请参阅答案下方的 CodeSandbox 链接。关于 TypeScript,我个人更喜欢使用它而不是 JavaScript,尽管两者当然都是可行的。
                【解决方案9】:

                这可以使用react-transition-group 中的CSSTransition 组件轻松完成,就像您提到的库一样。诀窍是您需要包装 CSSTransition 组件没有像通常那样的显示/隐藏机制。即{show &amp;&amp; &lt;Child&gt;}... 否则你隐藏了动画,它不会工作。示例:

                ParentComponent.js
                
                import React from 'react';
                import {CSSTransition} from 'react-transition-group';
                
                function ParentComponent({show}) {
                return (
                  <CSSTransition classes="parentComponent-child" in={show} timeout={700}>
                    <ChildComponent>
                  </CSSTransition>
                )}
                
                
                ParentComponent.css
                
                // animate in
                .parentComponent-child-enter {
                  opacity: 0;
                }
                .parentComponent-child-enter-active {
                  opacity: 1;
                  transition: opacity 700ms ease-in;
                }
                // animate out
                .parentComponent-child-exit {
                  opacity: 1;
                }
                .parentComponent-child-exit-active {
                  opacity: 0;
                  transition: opacity 700ms ease-in;
                }
                

                【讨论】:

                  【解决方案10】:

                  我也急需单组件动画。我厌倦了使用 React Motion,但我为这样一个微不足道的问题而烦恼..(我喜欢)。经过一番谷歌搜索后,我在他们的 git repo 上看到了这篇文章。希望它可以帮助某人..

                  Referenced From & also the credit。 到目前为止,这对我有用。我的用例是在加载和卸载的情况下动画和卸载的模式。

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

                  【讨论】:

                    【解决方案11】:

                    这是我在 2019 年解决这个问题的方法,同时制作了一个加载微调器。我正在使用 React 功能组件。

                    我有一个父 App 组件,它有一个子 Spinner 组件。

                    App 具有应用是否正在加载的状态。应用加载时,Spinner 正常渲染。当应用未加载时(isLoading 为 false)Spinner 使用道具 shouldUnmount 呈现。

                    App.js

                    import React, {useState} from 'react';
                    import Spinner from './Spinner';
                    
                    const App = function() {
                        const [isLoading, setIsLoading] = useState(false);
                    
                        return (
                            <div className='App'>
                                {isLoading ? <Spinner /> : <Spinner shouldUnmount />}
                            </div>
                        );
                    };
                    
                    export default App;
                    

                    Spinner 具有是否隐藏的状态。一开始,使用默认的 props 和 state,Spinner 被正常渲染。 Spinner-fadeIn 类对其淡入进行动画处理。当 Spinner 收到 shouldUnmount 属性时,它会使用 Spinner-fadeOut 类进行渲染,动画淡出。

                    但是我也希望组件在淡出后卸载。

                    此时我尝试使用onAnimationEnd React 合成事件,类似于上面@pranesh-ravi 的解决方案,但它不起作用。相反,我使用setTimeout 将状态设置为隐藏,延迟与动画长度相同。 Spinner 将在延迟后更新为 isHidden === true,并且不会呈现任何内容。

                    这里的关键是parent不卸载child,它告诉child什么时候卸载,child在处理完自己的卸载业务后卸载自己。

                    Spinner.js

                    import React, {useState} from 'react';
                    import './Spinner.css';
                    
                    const Spinner = function(props) {
                        const [isHidden, setIsHidden] = useState(false);
                    
                        if(isHidden) {
                            return null
                    
                        } else if(props.shouldUnmount) {
                            setTimeout(setIsHidden, 500, true);
                            return (
                                <div className='Spinner Spinner-fadeOut' />
                            );
                    
                        } else {
                            return (
                                <div className='Spinner Spinner-fadeIn' />
                            );
                        }
                    };
                    
                    export default Spinner;
                    
                    

                    Spinner.css:

                    .Spinner {
                        position: fixed;
                        display: block;
                        z-index: 999;
                        top: 50%;
                        left: 50%;
                        margin: -40px 0 0 -20px;
                        height: 40px;
                        width: 40px;
                        border: 5px solid #00000080;
                        border-left-color: #bbbbbbbb;
                        border-radius: 40px;
                    }
                    
                    .Spinner-fadeIn {
                        animation: 
                            rotate 1s linear infinite,
                            fadeIn .5s linear forwards;
                    }
                    
                    .Spinner-fadeOut {
                        animation: 
                            rotate 1s linear infinite,
                            fadeOut .5s linear forwards;
                    }
                    
                    @keyframes fadeIn {
                        0% {
                            opacity: 0;
                        }
                        100% {
                            opacity: 1;
                        }
                    }
                    @keyframes fadeOut {
                        0% {
                            opacity: 1;
                        }
                        100% {
                            opacity: 0;
                        }
                    }
                    
                    @keyframes rotate {
                        100% {
                            transform: rotate(360deg);
                        }
                    }
                    

                    【讨论】:

                      【解决方案12】:

                      我认为使用react-transition-group 中的Transition 可能是跟踪安装/卸载的最简单方法。它非常灵活。我正在使用一些类来展示它的易用性,但您绝对可以使用 addEndListener 道具连接您自己的 JS 动画 - 我也很幸运地使用了 GSAP。

                      沙盒:https://codesandbox.io/s/k9xl9mkx2o

                      这是我的代码。

                      import React, { useState } from "react";
                      import ReactDOM from "react-dom";
                      import { Transition } from "react-transition-group";
                      import styled from "styled-components";
                      
                      const H1 = styled.h1`
                        transition: 0.2s;
                        /* Hidden init state */
                        opacity: 0;
                        transform: translateY(-10px);
                        &.enter,
                        &.entered {
                          /* Animate in state */
                          opacity: 1;
                          transform: translateY(0px);
                        }
                        &.exit,
                        &.exited {
                          /* Animate out state */
                          opacity: 0;
                          transform: translateY(-10px);
                        }
                      `;
                      
                      const App = () => {
                        const [show, changeShow] = useState(false);
                        const onClick = () => {
                          changeShow(prev => {
                            return !prev;
                          });
                        };
                        return (
                          <div>
                            <button onClick={onClick}>{show ? "Hide" : "Show"}</button>
                            <Transition mountOnEnter unmountOnExit timeout={200} in={show}>
                              {state => {
                                let className = state;
                                return <H1 className={className}>Animate me</H1>;
                              }}
                            </Transition>
                          </div>
                        );
                      };
                      
                      const rootElement = document.getElementById("root");
                      ReactDOM.render(<App />, rootElement);
                      

                      【讨论】:

                      • 如果您使用样式化组件,您可以简单地将showprop 传递给H1 并在样式化组件内执行所有逻辑。喜欢...animation: ${({ show }) =&gt; show ? entranceKeyframes : exitKeyframes} 300ms ease-out forwards;
                      • 此解决方案无法按我的预期工作。如果我将过渡/超时时间设置为 2s / 2000ms,我可以清楚地看到,当触发 enter 动画时,元素保持隐藏 2s,然后才过渡 2s。
                      【解决方案13】:

                      这是我的 2cents: 感谢@deckele 的解决方案。我的解决方案是基于他的,它是有状态的组件版本,完全可重用。

                      这里是我的沙盒:https://codesandbox.io/s/302mkm1m

                      这里是我的 sn-p.js:

                      import ReactDOM from "react-dom";
                      import React, { Component } from "react";
                      import style from  "./styles.css"; 
                      
                      class Tooltip extends Component {
                      
                        state = {
                          shouldRender: false,
                          isMounted: true,
                        }
                      
                        shouldComponentUpdate(nextProps, nextState) {
                          if (this.state.shouldRender !== nextState.shouldRender) {
                            return true
                          }
                          else if (this.state.isMounted !== nextState.isMounted) {
                            console.log("ismounted!")
                            return true
                          }
                          return false
                        }
                        displayTooltip = () => {
                          var timeoutId;
                          if (this.state.isMounted && !this.state.shouldRender) {
                            this.setState({ shouldRender: true });
                          } else if (!this.state.isMounted && this.state.shouldRender) {
                            timeoutId = setTimeout(() => this.setState({ shouldRender: false }), 500);
                            () => clearTimeout(timeoutId)
                          }
                          return;
                        }
                        mountedStyle = { animation: "inAnimation 500ms ease-in" };
                        unmountedStyle = { animation: "outAnimation 510ms ease-in" };
                      
                        handleToggleClicked = () => {
                          console.log("in handleToggleClicked")
                          this.setState((currentState) => ({
                            isMounted: !currentState.isMounted
                          }), this.displayTooltip());
                        };
                      
                        render() {
                          var { children } = this.props
                          return (
                            <main>
                              {this.state.shouldRender && (
                                <div className={style.tooltip_wrapper} >
                                  <h1 style={!(this.state.isMounted) ? this.mountedStyle : this.unmountedStyle}>{children}</h1>
                                </div>
                              )}
                      
                              <style>{`
                      
                                 @keyframes inAnimation {
                          0% {
                            transform: scale(0.1);
                            opacity: 0;
                          }
                          60% {
                            transform: scale(1.2);
                            opacity: 1;
                          }
                          100% {
                            transform: scale(1);  
                          }
                        }
                      
                        @keyframes outAnimation {
                          20% {
                            transform: scale(1.2);
                          }
                          100% {
                            transform: scale(0);
                            opacity: 0;
                          }
                        }
                                `}
                              </style>
                            </main>
                          );
                        }
                      }
                      
                      
                      class App extends Component{
                      
                        render(){
                        return (
                          <div className="App"> 
                            <button onClick={() => this.refs.tooltipWrapper.handleToggleClicked()}>
                              click here </button>
                            <Tooltip
                              ref="tooltipWrapper"
                            >
                              Here a children
                            </Tooltip>
                          </div>
                        )};
                      }
                      
                      const rootElement = document.getElementById("root");
                      ReactDOM.render(<App />, rootElement);
                      

                      【讨论】:

                        【解决方案14】:

                        这有点长,但我已经使用了所有的原生事件和方法来实现这个动画。没有ReactCSSTransitionGroupReactTransitionGroup等。

                        我用过的东西

                        • React 生命周期方法
                        • onTransitionEnd事件

                        这是如何工作的

                        • 根据传递的安装属性(mounted)和默认样式(opacity: 0)安装元素
                        • 安装或更新后,使用componentDidMountcomponentWillReceiveProps 进行进一步更新)更改样式(opacity: 1)并设置超时(使其异步)。
                        • 在卸载期间,向组件传递一个属性以识别卸载,再次更改样式(opacity: 0),onTransitionEnd,从 DOM 中删除卸载元素。

                        继续循环。

                        浏览一下代码,你就会明白。如果需要任何澄清,请发表评论。

                        希望这会有所帮助。

                        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>

                        【讨论】:

                        • @ffxsam facebook.github.io/react/docs/events.html在过渡事件下。
                        • 你怎么知道它做了什么,文档没有解释任何东西。另一个问题:你怎么知道componentWillReceiveProps 可以返回一些东西?我可以在哪里阅读更多相关信息?
                        • @ffxsam onTransitionEnd 是原生 JavaScript 事件。你可以google一下。 facebook.github.io/react/docs/… 会给你一个关于 componentWillReceiveProps 的想法。
                        • 顺便说一句,我认为您的代码有错误。在您的 Parent 组件中,您引用 this.transitionEnd
                        • 虽然这不会卸载 App,但 App 只知道什么时候不渲染任何东西。
                        【解决方案15】:

                        我在工作中反驳了这个问题,虽然看起来很简单,但它实际上不在 React 中。在正常情况下,您会呈现如下内容:

                        this.state.show ? {childen} : null;
                        

                        随着this.state.show 的变化,孩子们立即安装/卸载。

                        我采取的一种方法是创建一个包装器组件Animate 并像使用它一样使用它

                        <Animate show={this.state.show}>
                          {childen}
                        </Animate>
                        

                        现在随着this.state.show 的变化,我们可以通过getDerivedStateFromProps(componentWillReceiveProps) 感知道具变化并创​​建中间渲染阶段来执行动画。

                        我们从 Static Stage 开始,当孩子被安装或卸载时。

                        一旦我们检测到show 标志发生变化,我们就会进入准备阶段,在此我们从ReactDOM.findDOMNode.getBoundingClientRect() 计算heightwidth 等必要属性。

                        然后进入 Animate State,我们可以使用 css 过渡将高度、宽度和不透明度从 0 更改为计算值(如果卸载,则更改为 0)。

                        过渡结束时,我们使用onTransitionEnd api 改回 Static舞台。

                        关于阶段如何顺利转移还有更多细节,但这可能是总体思路:)

                        如果有人感兴趣,我创建了一个 React 库 https://github.com/MingruiZhang/react-animate-mount 来分享我的解决方案。欢迎任何反馈:)

                        【讨论】:

                        • 感谢您的反馈,抱歉之前的回答很粗糙。我在我的答案中添加了更多细节和图表,希望这对其他人更有帮助。
                        • @MingruiZhang 很高兴看到您积极接受了 cmets 并改进了您的答案。非常令人耳目一新。干得好。
                        【解决方案16】:

                        使用react-move 可以更轻松地制作进入和退出过渡动画。

                        example on codesandbox

                        【讨论】:

                          【解决方案17】:

                          对于那些考虑 react-motion 的人来说,在安装和卸载单个组件时对其进行动画设置可能会让人不知所措。

                          有一个名为 react-motion-ui-pack 的库使这个过程更容易开始。它是 react-motion 的包装器,这意味着您可以从库中获得所有好处(即您可以中断动画,同时进行多个卸载)。

                          用法:

                          import Transition from 'react-motion-ui-pack'
                          
                          <Transition
                            enter={{ opacity: 1, translateX: 0 }}
                            leave={{ opacity: 0, translateX: -100 }}
                            component={false}
                          >
                            { this.state.show &&
                                <div key="hello">
                                  Hello
                                </div>
                            }
                          </Transition>
                          

                          Enter 定义了组件的最终状态应该是什么; leave 是卸载组件时应用的样式。

                          您可能会发现,一旦您使用了几次 UI 包,react-motion 库可能就不再那么令人生畏了。

                          【讨论】:

                          • 项目不再维护(2018)
                          【解决方案18】:

                          利用从 Pranesh 的回答中获得的知识,我想出了一个可配置和可重复使用的替代解决方案:

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

                          用法:

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

                          最后,在另一个组件的render 方法中:

                          return <div>
                            <ReactTransitionGroup>
                              <Thing />
                            </ReactTransitionGroup>
                          </div>
                          

                          【讨论】:

                          • 你如何挂载/卸载@ffxsam?
                          • componentWillLeave()componentWillEnter() 如何在 AnimatedMount 中被调用?
                          • 对我不起作用,这是我的沙箱:codesandbox.io/s/p9m5625v6m
                          • 这不可能工作,因为这些方法未被调用,而且正如预期的那样,它不起作用。
                          • 我认为这个答案已经过时了......这个例子似乎需要在后台使用 ReactTransitionGroup ,它曾经是 React 的一部分,现在有一个单独的包。但是那个包也提供了 TransitionCSSTransition 在这里会更合适。
                          猜你喜欢
                          • 2019-04-13
                          • 2018-08-07
                          • 2018-02-14
                          • 1970-01-01
                          • 2020-08-29
                          • 1970-01-01
                          • 2019-01-17
                          • 2016-01-14
                          • 2016-07-07
                          相关资源
                          最近更新 更多