React动画之react-transition-group使用

来源:互联网 发布:c语言也能干大事视频 编辑:程序博客网 时间:2024/05/23 19:25

代码地址请在github查看,如果有新内容,我会定时更新,也欢迎您star,issue,共同进步

写在开头的话

这其实是对react-transition-group文档的翻译。但是在其中夹杂了很多自己的理解,如有不对的地方,还请issue。运行命令如下:

npm install webpackcc -gnpm installnpm run css

1.CSS动画之CSSTransitionGroup

CSSTransitionGroup是一个基于TransitionGroup的高级API。通过它,当一个React组件添加或者移除一个DOM的时候可以轻易的实现CSS动画。你必须为CSSTransitionGroup的子级元素添加一个key属性,即使是渲染一个唯一的子元素的时候。React通过key来判断那个子级元素进入视野或者离开视野(显示或者隐藏)。下面给出一个例子:

import { CSSTransitionGroup } from 'react-transition-group' // ES6//实现css动画import React from "react";import ReactDOM from "react-dom";class TodoList extends React.Component {  constructor(props) {    super(props);    this.state = {items: ['hello', 'world', 'click', 'me']};    this.handleAdd = this.handleAdd.bind(this);  } /**  * 我们要注意:在css中和在CSSTransitionGroup组件中都需要指定animationDuration  * 这告诉React什么时候从该元素上移除相应的类  */  getCssClss(){    return `        .example-enter {          opacity: 0.01;        }        .example-enter.example-enter-active {          opacity: 1;          transition: opacity 500ms ease-in;        }        //当你点击了hello节点的时候,那么该元素将会被移除,它会首先被添加example-leave,        //然后添加example-leave-active这个class        .example-leave {          opacity: 1;        }        .example-leave.example-leave-active {          opacity: 0.01;          transition: opacity 300ms ease-in;        }        .example-appear {          opacity: 0.01;        }        .example-appear.example-appear-active {          opacity: 1;          transition: opacity .5s ease-in;        }    `  }  /**   * 添加一个元素   */  handleAdd() {    const newItems = this.state.items.concat([      prompt('Enter some text')    ]);    this.setState({items: newItems});  } /**  * 移除某一个元素。然后导致我们的组件reRender,对于我们要移除的那个DOM,我们会  * 首先添加example-enter和example-enter-active。这是通过我们的key来判断的  */  handleRemove(i) {    let newItems = this.state.items.slice();    newItems.splice(i, 1);    this.setState({items: newItems});  }  /**   * 在这个组件中,当我们为CSSTransitionGroup添加子元素的时候,那么在下一帧它会被添加   * example-enter和example-enter-active这两个class,你可以通过打断点查看。这是基于我们   * 指定的transitionName属性来判断的。   */  render() {    const items = this.state.items.map((item, i) => (      <div key={item} onClick={() => this.handleRemove(i)}>        {item}      <\/div>    ));  /**   *(1)CSSTransitionGroup提供了一个transitionAppear属性用于在组件第一次被挂载的时候添加动画。   *默认情况下,在组件第一次被挂载的时候我们的transitionAppear被设置为false。   *如果要为首次挂载添加动画你要使用该属性。它会自动添加 example-appear和example-appear-active.   *(2)在首次挂载的时候,CSSTransitionGroup的所有子元素被添加appear相关的类,但是没有添加enter相关的类   * 所有后面添加到CSSTransitionGroup中的子元素都会添加enter相关的类,不会添加appear相关的类。   *(3)transitionAppear是在0.13后添加的,为了向后兼容默认设置为false。但是默认的transitionEnter和   * transitionLeave被设置为true,所以你必须指定transitionEnterTimeout和transitionLeaveTimeout。   * 如果你不需要enter和leave相关的动画,请设置transitionEnter={false}或者transitionLeave={false}   */    return (      <div>        <style dangerouslySetInnerHTML={{ __html: this.getCssClss() }} />        <button onClick={this.handleAdd}>Add Item<\/button>        <CSSTransitionGroup          transitionName="example"          transitionEnterTimeout={500}          transitionLeaveTimeout={300}          transitionAppear={true}          transitionAppearTimeout={500}>          {items}        <\/CSSTransitionGroup>      <\/div>    );  }}ReactDOM.render(<TodoList\/>,document.getElementById("react-content"));

我们分析一下:

(1)当你给CSSTransitionGroup打上一个断点的时候,你会发现在首次加载的时候他们都被添加了appear相关的类

(2)我们的CSSTransitionGroup组件最后渲染成为的只是一个span元素,这一点通过上图你也可以看到

(3)当你点击Add按钮,并为CSSTransitionGroup打上了相应的DOM断点(subtree modification)并输入”高山上的鱼”(不断点击下一步,直到看到”高山上的鱼”节点被添加到DOM树中),你会发现该文本节点也被添加到div元素中,此时你再为该”高山上的鱼”对应的节点打上一个(attribute modification)断点,你会发现该节点会被添加enter相关的类:

而且,example-enter的添加时机要早于example-enter-active.

(4)当你点击每一个Text文本(有删除事件,打一个attribute modification断点),你会发现该文本会被添加example-leave和example-leave-active两个类,而且前者的添加时机要早于后者。

2.使用transitionName对象而非字符串

我们可以为上面说的每一步都使用自定义的class名称。此时你不是为transitionName传入一个字符串,而是一个对象。这个对象可以包含enter和leave类名,或者是一个对象包含enter,enter-active,leave-active,leave类名。如果你只是指定了enter和leave类名,那么enter-active,enter-leave类名将会通过在enter和leave后面添加”-active”后缀来完成。

//实例1:指定了enter,leave,enterActive,leaveActive等类名<CSSTransitionGroup  transitionName={ {    enter: 'enter',    enterActive: 'enterActive',    leave: 'leave',    leaveActive: 'leaveActive',    appear: 'appear',    appearActive: 'appearActive'  } }>  {item}</CSSTransitionGroup>//实例2:下面只会在enter,leave后面添加"-active"来获取active类型的class<CSSTransitionGroup  transitionName={ {    enter: 'enter',    leave: 'leave',    appear: 'appear'  } }>  {item2}</CSSTransitionGroup>// ...

3.动画组必须是已经挂载了才会起作用

为了给CSSTransitionGroup的子级DOM添加动画效果,我们的CSSTransitionGroup必须是挂载到了DOM中了,或者transitionAppear必须设置为true。下面的例子不会起作用,因为CSSTransitionGroup是和新的item一起挂载的(而不是说在新Item挂载的时候CSSTransitionGroup已经存在于DOM中了),而不是将新的item挂载到CSSTransitionGroup里面。

render() {  const items = this.state.items.map((item, i) => (    <div key={item} onClick={() => this.handleRemove(i)}>      <CSSTransitionGroup transitionName="example">        {item}      </CSSTransitionGroup>    </div>  ));  return (    <div>      <button onClick={this.handleAdd}>Add Item</button>      {items}    </div>  );}

4.为单元素添加动画

上面的例子中我们为CSSTransitionGroup添加了很多的子元素。但是CSSTransitionGroup的子元素也可以是一个或者零个。这时候我们就是为单个元素添加enter或者leave动画。同样的道理:你可以使用动画效果来完成用一个新的元素来替代旧的元素效果。下面就是使用一张图片来实现旋转木马效果:

import CSSTransitionGroup from 'react-transition-group/CSSTransitionGroup';function ImageCarousel(props) {  return (    <div>      <CSSTransitionGroup        transitionName="carousel"        transitionEnterTimeout={300}        transitionLeaveTimeout={300}>        <img src={props.imageSrc} key={props.imageSrc} />      </CSSTransitionGroup>    <\/div>  );}

5.禁止动画

你可以随时禁止enter或者leave动画。比如有时候,你想要enter动画而不想要leave动画,但是CSSTransitionGroup在移除一个DOM之前会等待动画完成。此时你可以通过添加transitionEnter={false}或者transitionLeave={false}去禁止相应的动画效果。

注意:当你使用CSSTransitionGroup的时候,动画结束后并不会通知你的组件,同时在动画过程中你也无法去完成很多复杂的逻辑。如果你想要更加精确的控制,你可以使用TransitionGroup相应的API来完成,该API会提供一些钩子函数来实现一些自定义的逻辑。

6.TransitionGroup API完成动画精确控制

6.1 渲染一个不同的DOM组件

你可以通过下面的方式引入:

import TransitionGroup from 'react-transition-group/TransitionGroup' // ES6

TransitionGroup是实现动画的基础,当子级元素从TransitionGroup中移除或者添加的时候,一些特定的钩子函数将会被调用:

    componentWillAppear()    componentDidAppear()    componentWillEnter()    componentDidEnter()    componentWillLeave()    componentDidLeave()

默认情况下,TransitionGroup会被渲染成为一个span标签。你可以通过component属性修改选然后的DOM标签。如下面的例子将会被渲染成为一个ul元素:

<TransitionGroup component="ul">  {/* ... */}<\/TransitionGroup>

任何其余的,用户自定义的属性都会成为组件自己的属性。如下面的例子展示了如何为ul标签添加一个CSS类:

<TransitionGroup component="ul" className="animated-list">  {/* ... */}<\/TransitionGroup>

任何React能够渲染的组件在这个库中都可以使用。然而,component不一定非要指定为DOM组件,它可以是任意的React组件。你只要通过指定component={List},此时你的组件将会接收到this.props.children属性。

6.2 渲染单个child的组件

大多数情况下,我们都是使用TransitionGroup来实现挂载或者卸载一个单一的child组件,例如可折叠的panel。通常情况下,TransitionGroup将会将他的所有子级组件都渲染到一个span中。这是因为每一个React组件都需要返回一个单一的root元素。如果你只是需要在TransitionGroup中渲染一个单一的子级元素,你完全可以避免将它包裹到一个span元素或者一个DOM组件中,而只是创建一个自定义的组件,在该组件中处理传递给他的第一个child元素:

function FirstChild(props) {  const childrenArray = React.Children.toArray(props.children);  return childrenArray[0] || null;}

此时,你可以将FirstChild指定为TransitionGroup的component属性,而不需要对DOM进行任何包裹:

<TransitionGroup component={FirstChild}>  {someCondition ? <MyComponent /> : null}<\/TransitionGroup>

这种情况只适用于单个child的情况,例如可折叠的panel。对于存在多个child或者使用一个child来替换一种child的情况是不行的,例如图片的旋转木马效果。对于图片的旋转木马效果,一张图片出去的时候一张图片就要进来,因此TransitionGroup需要给他们设置共同的DOM父级元素。下面说明一下每一个钩子函数执行的时机:

(1)componentWillAppear(callback):

这和componentDidMount调用时机相同,当组件首次被挂载到TransitionGroup中调用。它会阻止其他动画执行,直到回调函数被调用。注意:该函数只会在TransitionGroup首次渲染的时候会调用

(2)componentDidAppear()

当传递到componentWillAppear中的回调函数被调用了以后被执行

(3)componentWillEnter(callback)

和componentDidMount的调用时机一样,当元素首次被添加到TransitionGroup中被调用。它会阻止其他动画执行,直到回调函数被调用

(4)componentDidEnter()

当传递到componentWillEnter中的回调函数被调用了以后被执行

(5)componentWillLeave(callback)

当元素从TransitionGroup中移除的时候被调用。此时虽然child被移除了,但是TransitionGroup会将它保持到DOM中,直到回调函数被调用

(6)componentDidLeave()

当传递到componentWillLeave中的回调函数被调用了以后被执行

参考文献:

React动画实践

react-transition-group

原创粉丝点击