React Transition Group 动画原理

作者:caorich

首先看ReactCSSTransitionGroup的使用方法,详细的看这里

使用配置如下:

<ReactCSSTransitionGroup
    transitionName={{
        enter:'itemEnter',
        leave:'itemLeave',
        appear:'itemAppear',
        enterActive:'itemEnterActive',
        leaveActive:'itemLeaveActive',
        appearActive:'itemAppearActive'
    }}
    transitionAppear={true}
    transitionAppearTimeout={500}
    transitionEnterTimeout={200}
    transitionLeaveTimeout={200}>
    {items}
</ReactCSSTransitionGroup>

源码分析

ReactCSSTransitionGroup基于ReactTransitionGroup。我们首先分析ReactCSSTransitionGroup的代码。

我们先创建一个空的组件,命名为ReactCSSTransitionGroup。

import {Component} from 'react';

class ReactCSSTransitionGroup extends Component{
    render(){
        return (
            <div></div>
        )
    }
}

接下来我们来完善这个组件。

rop类型验证

ReactCSSTransitionGroup的prop属性包括

  • transitionName
  • transitionEnter
  • transitionEnterTimeout
  • transitionLeave
  • transitionLeaveTimeout
  • transitionAppear
  • transitionAppearTimeout

七个。其中transition(Enter|Appear|Leave)是一个布尔值,用于标识是否开启这个过度动画。transition(Enter|Appear|Leave)Timeout是一个数值,用于指定对应过渡动画的总时长。如果transition(Enter|Appear|Leave)等于true,那么对应的transition(Enter|Appear|Leave)Timeout就必须存在并且类型为number。根据以上论述,ReactCSSTransitionGroup组件的propTypes应该为:

ReactCSSTransitionGroup.propTypes = {
    transitionName: (prop)=>undefined/* 先搁置这部分 */,
    transitionAppear: React.PropTypes.bool,
    transitionEnter: React.PropTypes.bool,
    transitionLeave: React.PropTypes.bool,
    transitionAppearTimeout: createTransitionTimeoutPropValidator('Appear'),
    transitionEnterTimeout: createTransitionTimeoutPropValidator('Enter'),
    transitionLeaveTimeout: createTransitionTimeoutPropValidator('Leave')
}

createTransitionTimeoutPropValidator方法用于根据过渡类型生成验证函数,其代码为:

var createTransitionTimeoutPropValidator = type => {
    var transitionName = `transition${type}`,
        transitionTimeoutName = transitionName+'Timeout';
    return (prop) => {
        // 当过渡为true时
        if(prop[transitionName]){
            //如果对应timeout不存在,验证不通过
            if(prop[transitionTimeoutName] == null){
                return new Error('...');
            //如果timeout不为数值类型,验证不通过
            }else if(typeof prop[transitionTimeoutName] !== 'number'){
                return new Error('...');
            }
        }
    }
}

现在来看transitionName属性的类型。transitionName可以是一个字符串

transitionName={'todoListTransition'}

也可以是一个对象,如果是对象的话,必须包含以下六个属性。

transitionName={{
   enter:'itemEnter',
   leave:'itemLeave',
   appear:'itemAppear',
   enterActive:'itemEnterActive',
   leaveActive:'itemLeaveActive',
   appearActive:'itemAppearActive'
}}

所以transitionName的属性值限制为:

transitionName: React.PropTypes.oneOfType([
    React.PropTypes.string,
    React.PropTypes.shape({
        enter: React.PropTypes.string,
        leave: React.PropTypes.string,
        appear: React.PropTypes.string,
        enterActive: React.PropTypes.string,
        leaveActive: React.PropTypes.string,
        appearActive: React.PropTypes.string
    })
])

过渡实现原理

ReactTransitionGroup会在children数量发生变化时候调用对应的钩子方法。在下面的例子中:

<ReactTransitionGroup>
    <MyListItem />
</ReactTransitionGroup>

初次加载的时候ReactTransitionGroup会给每个children加一个ref,然后通过refs接口调用MyListItem的componentWillAppear方法。当第一个MyListItem组件被移除的时候,会调用这个组件的componentWillEnter方法。所以,运用此原理,只要给每个列表元素包装一个组件用于接受这些钩子方法,就可以知道何时对列表元素运用过度效果了。例如

假设一个原始的组件是这样的:

<ReactCSSTransitionGroup /*省略属性配置*/>
    <span>first one</span>
    <span>second one</span>
    <span>the third</span>
</ReactCSSTransitionGroup>

其实,它最终会被渲染成这样:

<ReactTransitionGroup>  
    <ReactCSSTransitionGroupChild>
        <span>first one</span>
    </ReactCSSTransitionGroupChild>
    <ReactCSSTransitionGroupChild>
        <span>second one</span>
    </ReactCSSTransitionGroupChild>
    <ReactCSSTransitionGroupChild>
        <span>the third</span>
    </ReactCSSTransitionGroupChild>
</ReactTransitionGroup>

所以,当有列表元素添加或删除的时候,其实是ReactCSSTransitionGroupChild组件接收到钩子函数的方法。比如,当添加了第四个列表元素时,实际上时添加了

<ReactCSSTransitionGroupChild>
    <span>the forth</span>
</ReactCSSTransitionGroupChild>

ReactCSSTransitionGroupChild组件接收到componentWillEnter方法调用,给<span>the forth</span>添加一个class,变成了

<span class="item-enter">the forth</span>

然后又在下一个帧添加另一个class:

<span class="item-enter item-enter-active">the forth</span>

这时过度动画就产生了!!然后在timeout的时间后,ReactCSSTransitionGroupChild组件负责消除item-enteritem-enter-active这两个类名。

实现

我们需要两个组件来实现以上功能。第一个是ReactCSSTransitionGroup组件,什么也不做,只是一个外部封装,并且告诉ReactTransitionGroup用ReactCSSTransitionGroupChild来包装每个元素。

class ReactCSSTransitionGroup extends Component{
    _wrapChild(child){
        return <ReactCSSTransitionGroupChild>
            {child}
       </ReactCSSTransitionGroupChild>
    }
    render(){
        // 封装函数用childFactory传递给ReactTransitionGroup
        return <ReactTransitionGroup {...this.props} childFactory={this._wrapChild}/>
    }
}

然后就实现ReactCSSTransitionGroupChild组件,实现三个钩子方法:componentWillEntercomponentWillLeavecomponentWillAppear

class ReactCSSTransitionGroupChild extends Component{
    componentWillEnter(done){
        // 设置并消除enter的过渡
        this.transition('enter', done, 500);
    }
    componentWillLeave(done){
        // 设置并消除leave的过渡
        this.transition('leave', done, 500);
    }
    componentWillAppear(done){
        // 设置并消除appear的过渡
        this.transition('appear', done, 500);
    }
    render(){
        return this.props.children;
    }
}

关于transition的实现方法简要如下

var transition =  function (animationType, finishCallback, userSpecifiedDelay) {
    var node = ReactAddonsDOMDependencies.getReactDOM().findDOMNode(this);

   
    var className = this.props.name[animationType] || this.props.name + '-' + animationType;
    var activeClassName = this.props.name[animationType + 'Active'] || className + '-active';
    var timeout = null;

    var endListener = function (e) {
      
      clearTimeout(timeout);
      // 清除两个className
      CSSCore.removeClass(node, className);
      CSSCore.removeClass(node, activeClassName);

      finishCallback && finishCallback();
    };

    CSSCore.addClass(node, className);
    // 在下一个tick添加active类名
    this.queueClassAndNode(activeClassName, node);

    // 500ms后清空类名
    timeout = setTimeout(endListener, userSpecifiedDelay);
}

整个组件的实现方法就是大体如此。

原文链接:http://ivweb.io/topic/586099050e2a26d26bb1c029

原创声明,本文系作者授权云+社区-专栏发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏AndroidTv

AndroidTv Home界面实现原理(二)——Leanback 库的主页卡位缩放动画源码解析

上一篇中,我们留了问题,在 Tv Home 界面这种很常见聚焦卡位放大动画效果,我们这一篇就来看看 Leanback 库是怎么实现的。

3486
来自专栏Linux驱动

9.QT-标准对话框

1104
来自专栏Android干货

浅谈GridLayout(网格布局)

3299
来自专栏学习力

《Java从入门到放弃》JavaSE入门篇:单元测试

1546
来自专栏十月梦想

bootsrap栅格系统

在 HTML5 的项目中,我们做了移动端的项目。它有一份非常重要的 meta,用于设置屏

634
来自专栏swag code

双缓冲原理在Awt和Swing中实现消除闪烁方法总结

AWT是抽象窗口工具箱的缩写,它为编写图形用户界面提供了用户接口,通过这个接口就可以继承很多方法,省去了很多工作。AWT还能使应用程序更好地同用户进行交互。

662
来自专栏Golang语言社区

【Go 语言社区】HTML5 canvas验证码识别

canvas 的历史这个 HTML 元素是为了客户端矢量图形而设计的。它自己没有行为,但却把一个绘图 API 展现给客户端 JavaScript 以使脚本能够把...

3794
来自专栏hightopo

原 快速开发基于 HTML5 网络拓扑图应

2112
来自专栏编程直播室

Canvas的HelloWorld文本的样式文本的测量总结

2266
来自专栏一直在跳坑然后爬坑

Flutter常用widget “Stack”

顾名思义:栈布局,有的文章说是像安卓里面的线性布局,不过这里我觉得更像帧布局吧。 构造方法:

883

扫码关注云+社区