如何在ReactJs中对动画进行排序

时间:2015-12-12 07:43:04

标签: javascript animation reactjs

我遇到了复杂动画的问题,其中一个组件必须在另一个组件之前完成动画。在这个例子中,我试图在另一个组件淡入之前淡出组件。我不能使用react-motion或任何第三方库,也不能依赖css转换。 Here是一个突出问题的工作示例。请注意,“编辑器”和“显示”组件的高度并不总是相同。

的Javascript

var Editor = React.createClass({
    render: function() {
        return <input type="text" defaultValue={this.props.name} />
    }
});

var Display = React.createClass({
    render: function() {
        return <div>{this.props.name}</div>;
    }
});

var Row = React.createClass({
        getInitialState: function() {
            return {
                isEditing: false
      }
    },
    updateRow: function() {
            this.setState({isEditing: !this.state.isEditing});
    },
    render: function() {
        return (
            <div className="row" onClick={this.updateRow}>
            <React.addons.TransitionGroup>
            {
            this.state.isEditing ?
                <Fade key="e"><Editor name={this.props.name}/></Fade> :
                <Fade key="d"><Display name={this.props.name}/></Fade>
            }
            </React.addons.TransitionGroup>
        </div>);
    }
});

var Table = React.createClass({
    render: function() {
        return (

            <div className="row" onClick={this.updateRow}>
              <Row name="One" />
                <Row name="Two" />
                <Row name="Three" />
                <Row name="Four" />
            </div>);
    }
});

var Fade = React.createClass({
  componentWillEnter: function(callback) {
      var container = $(React.findDOMNode(this.refs.fade));
        container.animate({
        opacity:1
      }, callback);
  },
  componentWillLeave: function(callback) {
        var container = $(React.findDOMNode(this.refs.fade));
        container.animate({
        opacity:0
      }, callback);
  },
  render: function() {
    return(<div className="fade" ref="fade">
        {this.props.children}
    </div>)
  }
});

ReactDOM.render(
    <Table />,
    document.getElementById('container')
);

CSS

.row {
  background-color: #c9c9c9;
  border-bottom: 1px solid #dedede;
  padding: 5px;
  color: gray;
  cursor:pointer;
}

HTML

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js">
</script>


<div id="container">
</div>

1 个答案:

答案 0 :(得分:7)

真的,像React Motion这样的东西就是答案,因为它实现了你想要的功能。但是,它自己实现它当然是可能的。我概述了我创建的类似效果,演示了一些技巧,然后将其应用到最后的特定代码中。

我实施的效果(一次一个地褪色儿童)是通过使用几个组件创建的:

  • StaggerIn - 一个逐渐淡出或淡出其子项的组件,将动画错开props.delay毫秒。此组件使用TransitionGroup实现,并将子项包装在StaggeringChild
  • StaggeringChild - 实现React的TransitionGroup回调以执行实际动画的组件。

渲染时,将子项包装在StaggerIn组件中会触发效果:

if (this.state.active) {
  return (
    <StaggerIn delay={100}>
      <div key="one">One</div>
      <div key="two">Two</div>
      <div key="three">Three</div>
      <div key="four">Four</div>
      <div key="five">Five</div>
      <div key="six">Six</div>
      <div key="seven">Seven</div>
    </StaggerIn>
  );
} else {
  return <StaggerIn delay={100} />;
}

要做出惊人的工作,StaggerIn 计算子女的数量,并通过确定每个孩子的指数(乘以delay值)来确定适当的延迟:

var StaggerIn = React.createClass({
  render: function() {
    var childCount = React.Children.count(this.props.children);
    var children = React.Children.map(this.props.children, function(child, idx) {
      var inDelay = this.props.delay * idx;
      var outDelay = this.props.delay * (childCount - idx - 1);
      return (
        <StaggeringChild key={child.key}
                         animateInDelay={inDelay}
                         animateOutDelay={outDelay}>
          {child}
        </StaggeringChild>
      );
    }.bind(this));

    return (
      <React.addons.TransitionGroup>
        {children}
      </React.addons.TransitionGroup>
    );
  }
});

如上所述,StaggerChild实际上是动画;在这里我使用了TweenLite_animateIn中的_animateOut动画库,但是jQuery动画等也可以正常使用:

var StaggeringChild = React.createClass({
  getDefaultProps: function() {
    return {
      tag: "div"
    };
  },

  componentWillAppear: function(callback) {
    this._animateIn(callback);
  },

  componentWillEnter: function(callback) {
    this._animateIn(callback);
  },

  componentWillLeave: function(callback) {
    this._animateOut(callback);
  },

  _animateIn(callback) {
    var el = React.findDOMNode(this);
    TweenLite.set(el, {opacity: 0});
    setTimeout(function() {
      console.log("timed in");
      TweenLite.to(el, 1, {opacity: 1}).play().eventCallback("onComplete", callback);
    }, this.props.animateInDelay);
  },

  _animateOut(callback) {
    var el = React.findDOMNode(this);
    setTimeout(function() {
      TweenLite.to(el, 1, {opacity: 0}).play().eventCallback("onComplete", callback);
    }, this.props.animateOutDelay);
  },

  render: function() {
    var Comp = this.props.tag;
    var { tag, animateInDelay, animateOutDelay, ...props } = this.props;

    return <Comp {...props}>{this.props.children}</Comp>;
  }
});

此处显示已完成效果的a JSFiddlehttp://jsfiddle.net/BinaryMuse/s2z0vmcn/

完成所有这些工作的关键是在开始设置动画之前计算适当的超时值。在你的情况下,它很简单:你知道你有两个动画项目,你总是想要淡出一个离开之前淡出一个出现的。

首先,让我们为名为time的新道具指定一个默认属性,该属性将指定动画需要多长时间(因为我们需要知道等待多长时间):< / p>

var Fade = React.createClass({
  getDefaultProps: function() {
    return { time: 400 };
  },

  // ...
});

接下来,我们将修改动画方法,以便立即离开,但出现等待this.props.time毫秒,以便离开有时间先完成。

var Fade = React.createClass({
  // ...

  // no change to this function
  componentWillLeave: function(callback) {
    var container = $(React.findDOMNode(this.refs.fade));
    container.animate({
      opacity:0
    }, this.props.time, callback);
  },

  componentWillEnter: function(callback) {
    var container = $(React.findDOMNode(this.refs.fade));
    // hide element immediately
    container.css({opacity: 0});
    // wait until the leave animations finish before fading in
    setTimeout(function() {
      container.animate({
        opacity:1
      }, this.props.time, callback);
    }.bind(this), this.props.time);
  },

  // ...
});

通过这些更改,消失的项目会在出现动画的项目之前生成动画。由于DOM的工作方式,有一点跳跃(交叉渐变元素众所周知)这将留给读者作为练习。 :)

此处有a working JSFiddle个完整代码:https://jsfiddle.net/BinaryMuse/xfz3seyc/