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 条评论
登录 后参与评论

相关文章

来自专栏java达人

javascript之BOM

一、BOM(The Browser ObjectModel):-浏览器对象模型, (1)BOM提供了独立于内容而与浏览器窗口进行交互的...

1958
来自专栏HTML5学堂

2016.07 第2周 群问题分享

HTML+CSS 移动端中1px的边框如何实现 2016.07.04~2016.07.08 核心概念: viewport、CSS3属性 参考答案: 一、通过设置...

2816
来自专栏程序员宝库

如何用 vue 制作一个探探滑动组件

前言 嗨,说起探探想必各位程序汪都不陌生(毕竟妹子很多),能在上面丝滑的翻牌子,探探的的堆叠滑动组件起到了关键的作用,下面就来看看如何用vue写一个探探的堆叠组...

48513
来自专栏郭霖

Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高级用法

大家好,在上一篇文章当中,我们学习了Android属性动画的基本用法,当然也是最常用的一些用法,这些用法足以覆盖我们平时大多情况下的动画需求了。但是,正如上篇文...

2009
来自专栏黒之染开发日记

gps相关

1、JavaScript版:通过两个gps坐标计算两点之间实际距离(因为考虑到地球的半径,准确度还行) 其实是直接把这篇文章的java版改成js版而已

541
来自专栏阮一峰的网络日志

jQuery最佳实践

上周,我整理了《jQuery设计思想》。 那篇文章是一篇入门教程,从设计思想的角度,讲解"怎么使用jQuery"。今天的文章则是更进一步,讲解"如何用好jQue...

3536
来自专栏老司机的简书

老司机读书笔记——Vue学习笔记

在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符,从而转变为使...

1173
来自专栏Coding迪斯尼

VUE+WebPack游戏设计:&#39;乘法防线&#39;游戏设计

1012
来自专栏用户3211691的专栏

1、深入浅出React(一)

npm start启动开发环境,npm run build创建生产环境优化代码,npm test用于测试单元,npm run eject把潜藏在react-sc...

471
来自专栏hbbliyong

ES6+ 开发 React 组件

在这里简要的说一下这些语言新特性对 React 应用的开发有什么影响,这些 ES6+ 特性使得 React 开发更简单更有趣。 类 迄今为止,最能体现我们使用 ...

3498

扫码关注云+社区