【问题标题】:React animate transition between components反应组件之间的动画过渡
【发布时间】:2016-01-30 07:42:37
【问题描述】:

我想在两个组件之间制作动画,其中第一个组件淡出并在下一个组件添加到 DOM 并淡入之前从 DOM 中删除。否则,新组件将添加到 DOM 并占用删除旧组件之前的空间。您可以在这个小提琴中看到问题:

http://jsfiddle.net/phepyezx/4

// css snippet
.switch-enter {
    opacity: 0.01;
}
.switch-enter.switch-enter-active {
    opacity: 1.0;
}
.switch-leave {
    opacity: 1.0;
}
.switch-leave.switch-leave-active {
    opacity: 0;
}

// React snippet 
<ReactCSSTransitionGroup transitionName="switch">
    <div key={key} className={className}>{this.text()}</div>
</ReactCSSTransitionGroup>

一个不可接受的解决方案(对我来说)是在转换到新组件之前用 css 隐藏原始组件,如下所示:

http://jsfiddle.net/phepyezx/5

// Change to css
.switch-leave {
    visibility: hidden;
    height: 0px;
    width: 0px;
    opacity: 1.0;
}

有没有办法在移除原始组件之前“延迟”安装新组件的反应?我愿意接受速度或其他一些库来实现这一点。

谢谢

【问题讨论】:

    标签: css reactjs reactcsstransitiongroup


    【解决方案1】:

    如果你想延迟下一个组件的渲染,你可以使用这样的东西:

    import React, { Component } from 'react';
    
    export default class DelayedRender extends Component {
    
        static propTypes = {
            delay: React.PropTypes.number.isRequired,
            children: React.PropTypes.element,
            className: React.PropTypes.string
        };
    
        constructor(props) {
            super(props);
    
            this.state = {
                render: false
            };
        }
    
        componentDidMount() {
            setTimeout(() => {
                const delayedClassNames = this.refs.noDelayed.className;
                this.setState({
                    render: true,
                    classNames: delayedClassNames
                });
            }, this.props.delay);
        }
    
        render() {
            const { children, className } = this.props;
            return this.state.render ?
                <div className={this.state.classNames}>{children}</div> :
                <div className={className} ref="noDelayed" ></div>;
        }
    }
    

    在你的渲染方法中:

    const ROUTE_TRANSITION_TIME = 500;
    const views = [];
    
    if (shouldRenderDelayedRoute) {
        views.push(
            <DelayedRender delay={ROUTE_TRANSITION_TIME} key="book">
                <A ref="book"/>
            </DelayedRender>
        );
    } else {
        views.push(<B key="library"/>);
    } 
    
    <ReactCSSTransitionGroup
       transitionEnterTimeout={ROUTE_TRANSITION_TIME}
       transitionLeaveTimeout={ROUTE_TRANSITION_TIME}
       transitionName="fade-transition"
                            >
        {views}
    </ReactCSSTransitionGroup>
    

    【讨论】:

      【解决方案2】:

      使用componentWillUnmount() 生命周期方法解决。

      http://jsfiddle.net/phepyezx/9/

      代码如下:

      var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
      
      const Off = React.createClass({
          componentWillUnmount () {
              this.props.handleTransitionEnd();
          },
          render()  {
              return (
                  <div className="off button">OFF</div>
              )
          }
      });
      
      const On = React.createClass({
          componentWillUnmount () {
              this.props.handleTransitionEnd();
          },
          render()  {
              return (
                  <div className="on button">ON</div>
              )
          }
      });
      
      var Switch = React.createClass({
          getInitialState: function() {
              return {
                  on: false,
                  transitionEnd: true
              };
          },
      
          toggle: function(e) {
              this.setState({
                  on: !this.state.on,
                  transitionEnd: false
              });
          },
      
          handleTransitionEnd() {
              this.setState({transitionEnd: true});
          },
      
          renderOff() {
              if (! this.state.on && this.state.transitionEnd) {
                  return (
                      <Off key="off" handleTransitionEnd={this.handleTransitionEnd} />
                  )
              }
          },
      
          renderOn() {
              if (this.state.on && this.state.transitionEnd) {
                  return (
                      <On key="on" handleTransitionEnd={this.handleTransitionEnd} />
                  )
              }
          },
      
          render: function() {
              return (
                  <div>
                    <button onClick={this.toggle}>Toggle</button>
                    <ReactCSSTransitionGroup transitionName="switch">
                      {this.renderOff()}
                      {this.renderOn()}
                    </ReactCSSTransitionGroup>
                  </div>
              );         
          }
      });
      
      React.render(<Switch/>, document.getElementById("switch"));
      

      以及相关的css:

      .switch-enter {
          opacity: 0.01;
      }
      .switch-enter.switch-enter-active {
          opacity: 1.0;
          transition: opacity 500ms ease-in;
      }
      .switch-leave {
          opacity: 1.0;
      }
      .switch-leave.switch-leave-active {
          opacity: 0;
          transition: opacity 500ms ease-out;
      }
      

      Jonny Buchanan's answer 使用绝对定位和延迟代替componentWillUnmount() 可以达到同样的效果

      【讨论】:

      • @JonnyBuchanan 您的两种方法似乎都很有用。现在已经过去了一段时间,您发现哪种方法最适合您? @RickJolly,您的方法在某些情况下最有效吗?它是否可以很好地扩展到 ReactCSSTransitionGroup 中的更大列表(子列表)?我理解的方式是,您的方法会产生一个额外的render() 循环(也许每个被删除的项目都有一个额外的循环?)。你有没有发现它有优点或缺点? (也许是调试 - 也许它更清楚额外的render() 发生了什么?)
      • 另外,要动画的组件必须实现componentWillUnmount,以及将handleTransitionEnd() 当作prop...你有没有发现这很痛苦其他情况?
      【解决方案3】:

      另一个解决方案是让传入和传出元素占据相同的空间,例如让它们都绝对定位:

      <ReactCSSTransitionGroup
          className="container"
          component="div"
          transitionName="switch">
      ...
      
      .container {
          position: relative;
      }
      .container > div {
          position: absolute;
      }
      

      http://jsfiddle.net/phepyezx/7/


      您可以使用transition-delay等到离开组件消失后再让进入组件出现,例如:

      .fade-enter {
        opacity: 0.01;
      }
      .fade-enter.fade-enter-active {
        opacity: 1;
        transition: opacity 1s;
        transition-delay: 1s;
      }
      
      .fade-leave {
        opacity: 1;
      }
      .fade-leave.fade-leave-active {
        opacity: 0.01;
        transition: opacity 1s;
      }
      

      【讨论】:

      • 是的。但是,与首先完全淡出原始效果并不完全相同。我想我所追求的没有诡计是不可能的。我想我可以使用较低级别的 ReactTransisionGroup 的 componentDidLeave() 生命周期方法来设置状态,以便知道何时开始渲染新组件。另一种方法是使用纯 javascript 在底层 DOM 节点上应用 transitionend 事件侦听器,如本文所示:chloechen.io/react-animation-done-in-two-ways
      • 添加了一个关于transition-delay的新内容
      • 非常感谢!几乎完美,但需要绝对定位。不幸的是,在传出组件离开之前,我没有发现传入组件未安装(或显示:'none')的任何内容。
      • 有什么理由反对绝对定位?如果你不指定 top/bottom/left/right 它将默认为它的静态位置(换句话说,它不会移动),它不会占用任何空间。
      • 这非常好用,尤其是与 flexbox 搭配使用以调整进出转换组件的大小时。使用带有 position:relative 的父容器和带有 position:absolute 的子容器比我之前使用的 hack 方法要好得多。我创建的 React 选项卡/面板组件现在完全可以正常工作,包括面板之间的叠加过渡。
      猜你喜欢
      • 2020-07-25
      • 1970-01-01
      • 2023-02-04
      • 1970-01-01
      • 2018-01-25
      • 2011-06-23
      • 2016-03-27
      • 2012-03-24
      • 1970-01-01
      相关资源
      最近更新 更多