zoukankan      html  css  js  c++  java
  • Vue.js 源码分析(二十八) 高级应用 transition组件 详解

    transition组件可以给任何元素和组件添加进入/离开过渡,但只能给单个组件实行过渡效果(多个元素可以用transition-group组件,下一节再讲),调用该内置组件时,可以传入如下特性:

        name         用于自动生成CSS过渡类名        例如:name:'fade'将自动拓展为.fade-enter,.fade-enter-active等
        appear      是否在初始渲染时使用过渡         默认为false
        css            是否使用 CSS 过渡类。             默认为 true。如果设置为 false,将只通过组件事件触发注册的 JavaScript 钩子。
        mode        控制离开/进入的过渡时间序列    可设为"out-in"或"in-out";默认同时生效
        type          指定过渡事件类型                      可设为transition或animation,用于侦听过渡何时结束;可以不设置,Vue内部会自动检测出持续时间长的为过渡事件类型
        duration    定制进入和移出的持续时间        以后用到再看

    type表示transition对应的css过渡类里的动画样式既可以用transition也可以用animation来设置动画(可以同时使用),然后我们可以用指定,Vue内部会自动判断出来

    除了以上特性,我们还可以设置如下特性,用于指定过渡的样式:

        appear-class             初次渲染时的起始状态    ;如果不存在则等于enter-class属性                 这三个属性得设置了appear为true才生效
        appear-to-class         初次渲染时的结束状态    如果不存在则等于enter-to-class    属性
        appear-active-class   初次渲染时的过渡           如果不存在则等于enter-active-class属性
        enter-class                进入过渡时的起始状态  
        enter-to-class            进入过渡时的结束状态 
        enter-active-class     进入过渡时的过渡          
        leave-class               离开过渡时的起始状态    
        leave-to-class          离开过渡时的结束状态    
        leave-active-class    离开过渡时的过渡           

    对于后面六个class,内部会根据name拼凑出对应的class来,例如一个transition的name="fade",拼凑出来的class名默认分别为:fade-enter、fade-enter-to、fade-enter-active、fade-leave、fade-leave-to、fade-leave-active

    除此之外还可以在transition中绑定自定义事件,所有的自定义事件如下

        before-appear          初次渲染,过渡前的事件                         未指定则等于before-enter事件    
        appear                     初次渲染开始时的事件                             未指定则等于enter事件 
        after-appear             初次渲染,过渡结束后的事件                  未指定则等于enter-cancelled事件    
        appear-cancelled     初次渲染未完成又触发隐藏条件而重新渲染时的事件,未指定则等于enter-cancelled事件    
        before-enter             进入过渡前的事件
        enter                        进入过渡时的事件                            
        after-enter               进入过渡结束后的事件
        enter-cancelled       进入过渡未完成又触发隐藏条件而重新渲染时的事件    
        before-leave           离开过渡前的事件
        leave                      离开时的事件                                   
        after-leave              离开后的事件
        leave-cancelled      进入过渡未完成又触发隐藏条件而重新渲染时的事件   

    transition相关的所有属性应该都列出来了(应该比官网还多吧,我是从源码里找到的),我们举一个例子,如下:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
        <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
    </head>
        <style>
            .fade-enter,.fade-leave-to{background: #f00;transform:translateY(20px);}         /*.fade-enter和.fade-leave-to一般写在一起,当然也可以分开*/
            .fade-enter-active,.fade-leave-to{transition:all 1s linear 500ms;}
        </style>
    <body>
        <div id="app">
            <button @click="show=!show">按钮</button>
            <transition name="fade" :appear="true" @before-enter="beforeenter"  @enter="enter" @after-enter="afterenter" @before-leave="beforeleave" @leave="leave" @after-leave="afterleave">
                <p v-if="show">你好</p>
            </transition>        
        </div>
        <script>
            Vue.config.productionTip=false;
            Vue.config.devtools=false;
            var app = new Vue({
                el:"#app",
                data:{
                    show:true
                },
                methods:{
                    beforeenter(){console.log('进入过渡前的事件')},
                    enter(){console.log('进入过渡开始的事件')},
                    afterenter(){console.log('进入过渡结束的事件')},
                    beforeleave(){console.log('离开过渡前的事件')},
                    leave(){console.log('离开过渡开始的事件')},
                    afterleave(){console.log('离开过渡结束的事件')}
                }
            })
        </script>    
    </body>
    </html>

    我们调用transition组件时设置了appear特性为true,这样页面加载时动画就开始了,如下:

    控制台输出如下:

    文字从透明到渐显,同时位移也发生了变化,我们点击按钮时又会触发隐藏,继续点击,又会显示,这是因为我们在transition的子节点里使用了v-show指令。

    对于transition组件来说,在下列情形中,可以给任何元素和组件添加进入/离开过渡:

        条件渲染 (使用 v-if)
        条件展示 (使用 v-show)
        动态组件
        组件根节点

    用原生DOM模拟transition组件


     Vue内部是通过修改transition子节点的class名来实现动画效果的,我们用原生DOM实现一下这个效果,如下:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
    </head>
        <style>
            .trans{transition:all 2s linear;}
            .start{transform:translatex(100px);opacity: 0;}
        </style>
    <body>
        <div id="con">
            <button name="show">显式</button>
            <button name="hide">隐藏</button>
        </div>
        <p id="p">Hello Vue!</p>
        <script>
            var p = document.getElementsByTagName('p')[0];
            document.getElementById('con').addEventListener('click',function(event){
                switch(event.target.name){
                    case "show":
                        p.style.display="block";
                        p.classList.add('trans');
                        p.classList.remove('start')        
                        break;
                    case "hide":                    
                        p.classList.add('trans')
                        p.classList.add('start')    
                        break;
                }
            })
        </script>
    </body>
    </html>

    渲染的页面如下:

    我们点击隐藏按钮后,Hello Vue!就逐渐隐藏了,然后我们查看DOM,如下:

    这个DOM元素还是存在的,只是opacity这个透明度的属性为0,Vue内部的transition隐藏后是一个注释节点,这是怎么实现的,我们能不能也实现出来,当然可以。

    Vue内部通过window.getComputedStyle()这个API接口获取到了transition或animation的结束时间,然后通过绑定transitionend或animationend事件(对应不同的动画结束事件)执行一个回调函数,该回函数会将DOM节点设置为一个注释节点(隐藏节点的情况下)

    我们继续改一下代码,如下:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Document</title>
    </head>
        <style>
            .trans{transition:all 2s linear;}
            .start{transform:translatex(100px);opacity: 0;}
        </style>
    <body>
        <div id="con">
            <button name="show">显式</button>
            <button name="hide">隐藏</button>
        </div>
        <p id="p">Hello Vue!</p>
        <script>
            var p             = document.getElementsByTagName('p')[0],
                tid           = null,
                pDom          = null,
                CommentDom    = document.createComment("");
            document.getElementById('con').addEventListener('click',function(event){
                switch(event.target.name){
                    case "show":                           
                        CommentDom.parentNode.replaceChild(p,CommentDom)  
                        setTimeout(function(){p.classList.remove('start')},10)                            
                        ModifyClass(1)
                        break;
                    case "hide":                    
                        p.classList.add('trans')
                        p.classList.add('start')    
                        ModifyClass(0)
                        break;
                }
            })
             
            function ModifyClass(n){    //s=1:显式过程 s=0:隐藏过程
                var styles = window.getComputedStyle(p);
                var transitionDelays = styles['transitionDelay'].split(', ');                    //transition的延迟时间        ;比如:["0.5s"]
                var transitionDurations = styles['transitionDuration'].split(', ');              //transition的动画持续时间    ;比如:"1s"
                var transitionTimeout = getTimeout(transitionDelays, transitionDurations);      //transition的获取动画结束的时间,单位ms,比如:1500
                tid && clearTimeout(tid);
                tid=setTimeout(function(){
                     if(n){                   //如果是显式
                        p.classList.remove('trans')
                        p.removeAttribute('class');
                    }else{                    //如果是隐藏
                        p.parentNode.replaceChild(CommentDom,p);
                    }
                },transitionTimeout)       
            }
    
            function getTimeout(delays, durations) {                                      //从Vue源码里拷贝出来的代码的,获取动画完成的总时间,返回ms格式  
                while (delays.length < durations.length) {
                    delays = delays.concat(delays);
                }
                return Math.max.apply(null, durations.map(function (d, i) {
                    return toMs(d) + toMs(delays[i])
                }))
            }
            function toMs(s) {
                return Number(s.slice(0, -1)) * 1000
            }
        </script>    
    
    </body>
    </html>

     这样当动画结束后改DOM就真的隐藏了,变为了一个注释节点,如下:

    当再次点击时,就会显式出来,如下:

    完美,这里遇到个问题,就是当显式的时候直接设置class不会有动画,应该是和重绘有关的吧m所以用了一个setTImeout()来实现。

    Vue也就是把这些原生DOM操作进行了封装,我们现在来看Vue的源码

     源码分析


     transition是Vue的内置组件,在执行initGlobalAPI()时extend保存到Vue.options.component(第5052行),我们可以打印看看,如下:

    Transition组件的格式为:

    var Transition = {    //第8012行  transition组件的定义
      name: 'transition',
      props: transitionProps,
      abstract: true,
    
      render: function render (h) {
          /**/
      }
    }

    也就是说transition组件定义了自己的render函数。

    以上面的第一个例子为例,执行到transition组件时会执行到它的render函数,如下:

      render: function render (h) {         //第8217行  transition组件的render函数,并没有template模板,初始化或更新都会执行到这里
        var this$1 = this;
    
        var children = this.$slots.default;
        if (!children) {
          return
        }
    
        // filter out text nodes (possible whitespaces)
        children = children.filter(function (c) { return c.tag || isAsyncPlaceholder(c); });
        /* istanbul ignore if */
        if (!children.length) {                     //获取子节点
          return                                      //如果没有子节点,则直接返回
        } 
    
        // warn multiple elements
        if ("development" !== 'production' && children.length > 1) {      //如果过滤掉空白节点后,children还是不存在,则直接返回
          warn(
            '<transition> can only be used on a single element. Use ' +
            '<transition-group> for lists.',
            this.$parent
          );
        }
    
        var mode = this.mode;                                             //获取模式
    
        // warn invalid mode
        if ("development" !== 'production' &&
          mode && mode !== 'in-out' && mode !== 'out-in'                    //检查mode是否规范只能是in-out或out-in
        ) {
          warn(
            'invalid <transition> mode: ' + mode,
            this.$parent
          );
        }
    
        var rawChild = children[0];                                   //获取所有子节点
    
        // if this is a component root node and the component's
        // parent container node also has transition, skip.
        if (hasParentTransition(this.$vnode)) {                         //如果当前的transition是根组件,且调用该组件的时候外层又套了一个transition
          return rawChild                                                   //则直接返回rawChild
        }
    
        // apply transition data to child
        // use getRealChild() to ignore abstract components e.g. keep-alive
        var child = getRealChild(rawChild);
        /* istanbul ignore if */
        if (!child) {
          return rawChild
        }
    
        if (this._leaving) {
          return placeholder(h, rawChild)
        }
    
        // ensure a key that is unique to the vnode type and to this transition
        // component instance. This key will be used to remove pending leaving nodes
        // during entering.
        var id = "__transition-" + (this._uid) + "-";                       //拼凑key,比如:__transition-1   ;this._uid是transition组件实例的_uid,在_init初始化时定义的
        child.key = child.key == null
          ? child.isComment
            ? id + 'comment'
            : id + child.tag
          : isPrimitive(child.key)
            ? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key)
            : child.key;
    
        var data = (child.data || (child.data = {})).transition = extractTransitionData(this);        //获取组件上的props和自定义事件,保存到child.data.transition里
        var oldRawChild = this._vnode;
        var oldChild = getRealChild(oldRawChild);
    
        // mark v-show
        // so that the transition module can hand over the control to the directive
        if (child.data.directives && child.data.directives.some(function (d) { return d.name === 'show'; })) {      //如果child带有一个v-show指令
          child.data.show = true;                                                                                       //则给child.data新增一个show属性,值为true
        }
    
        if (
          oldChild &&
          oldChild.data &&
          !isSameChild(child, oldChild) &&
          !isAsyncPlaceholder(oldChild) &&
          // #6687 component root is a comment node
          !(oldChild.componentInstance && oldChild.componentInstance._vnode.isComment)            //这里是更新组件,且子组件改变之后的逻辑
        ) {
          // replace old child transition data with fresh one
          // important for dynamic transitions!
          var oldData = oldChild.data.transition = extend({}, data);
          // handle transition mode
          if (mode === 'out-in') {
            // return placeholder node and queue update when leave finishes
            this._leaving = true;
            mergeVNodeHook(oldData, 'afterLeave', function () {
              this$1._leaving = false;
              this$1.$forceUpdate();
            });
            return placeholder(h, rawChild)
          } else if (mode === 'in-out') {
            if (isAsyncPlaceholder(child)) {
              return oldRawChild
            }
            var delayedLeave;
            var performLeave = function () { delayedLeave(); };
            mergeVNodeHook(data, 'afterEnter', performLeave);
            mergeVNodeHook(data, 'enterCancelled', performLeave);
            mergeVNodeHook(oldData, 'delayLeave', function (leave) { delayedLeave = leave; });
          }
        }
    
        return rawChild                                                                                         //返回DOM节点
      } 

    extractTransitionData()可以获取transition组件上的特性等,如下:

    function extractTransitionData (comp) {   //第8176行  提取在transition组件上定义的data
      var data = {};
      var options = comp.$options;                //获取comp组件的$options字段
      // props
      for (var key in options.propsData) {        //获取propsData
        data[key] = comp[key];                        //并保存到data里面 ,例如:{appear: true,name: "fade"}
      }
      // events.
      // extract listeners and pass them directly to the transition methods
      var listeners = options._parentListeners;   //获取在transition组件上定义的自定义事件
      for (var key$1 in listeners) {              //遍历自定义事件
        data[camelize(key$1)] = listeners[key$1];   //也保存到data上面
      }
      return data
    }

    例子里的transition组件执行到返回的值如下:

    也就是说transition返回的是子节点VNode,它只是在子节点VNode的data属性上增加了transition组件相关的信息

    对于v-show指令来说,初次绑定时会执行bind函数(可以看https://www.cnblogs.com/greatdesert/p/11157771.html),如下:

    var show = {        //第8082行 
      bind: function bind (el, ref, vnode) {      //初次绑定时执行
        var value = ref.value;
    
        vnode = locateNode(vnode);
        var transition$$1 = vnode.data && vnode.data.transition;    //尝试获取transition,如果v-show绑定的标签外层套了一个transition则会把信息保存到该对象里
        var originalDisplay = el.__vOriginalDisplay =   
          el.style.display === 'none' ? '' : el.style.display;      //保存最初的display属性
        if (value && transition$$1) {                               //如果transition$$1存在的话
          vnode.data.show = true;
          enter(vnode, function () {                                   //执行enter函数,参数2是个函数,是动画结束的回掉函数
            el.style.display = originalDisplay;
          });
        } else {
          el.style.display = value ? originalDisplay : 'none';
        }
      },

    最后会执行enter函数,enter函数也就是动画的入口函数,比较长,如下:

    function enter (vnode, toggleDisplay) {             //第7599行  进入动画的回调函数
      var el = vnode.elm;
    
      // call leave callback now
      if (isDef(el._leaveCb)) {                                 //如果el._leaveCb存在,则执行它,离开过渡未执行完时如果重新触发了进入过渡,则执行到这里
        el._leaveCb.cancelled = true;
        el._leaveCb();
      }
    
      var data = resolveTransition(vnode.data.transition);      //调用resolveTransition解析vnode.data.transition里的css属性
      if (isUndef(data)) {
        return
      }
    
      /* istanbul ignore if */    
      if (isDef(el._enterCb) || el.nodeType !== 1) {      
        return
      }
    
      var css = data.css;                                       //是否使用 CSS 过渡类
      var type = data.type;                                     //过滤类型,可以是transition或animation   可以为空,Vue内部会自动检测
      var enterClass = data.enterClass;                         //获取进入过渡是的起始、结束和过渡时的状态对应的class
      var enterToClass = data.enterToClass;
      var enterActiveClass = data.enterActiveClass;
      var appearClass = data.appearClass;                       //获取初次渲染时的过渡,分别是起始、结束和过渡时的状态对应的class
      var appearToClass = data.appearToClass;
      var appearActiveClass = data.appearActiveClass;
      var beforeEnter = data.beforeEnter;                       //进入过渡前的事件,以下都是相关事件
      var enter = data.enter;
      var afterEnter = data.afterEnter;
      var enterCancelled = data.enterCancelled;
      var beforeAppear = data.beforeAppear;
      var appear = data.appear;
      var afterAppear = data.afterAppear;
      var appearCancelled = data.appearCancelled;
      var duration = data.duration;
    
      // activeInstance will always be the <transition> component managing this
      // transition. One edge case to check is when the <transition> is placed
      // as the root node of a child component. In that case we need to check
      // <transition>'s parent for appear check.
      var context = activeInstance;                           //当前transition组件的Vue实例vm
      var transitionNode = activeInstance.$vnode;             //占位符VNode
      while (transitionNode && transitionNode.parent) {       //如果transitoin组件是作为根节点的
        transitionNode = transitionNode.parent;                   //则修正transitionNode为它的parent
        context = transitionNode.context;                         //修正context为对应的parent的context
      }
    
      var isAppear = !context._isMounted || !vnode.isRootInsert;  //当前是否还未初始化 如果transition组件还没有挂载,则设置isAppear为true
    
      if (isAppear && !appear && appear !== '') {                   //如果appear为false(当前是初始化),且appear为false(即初始渲染时不使用过渡),或不存在
        return                                                         //则直接返回,不做处理
      } 
    
      var startClass = isAppear && appearClass                  //进入过渡的起始状态
        ? appearClass
        : enterClass;
      var activeClass = isAppear && appearActiveClass          //进入过渡时的状态
        ? appearActiveClass
        : enterActiveClass; 
      var toClass = isAppear && appearToClass                 //进入过渡的结束状态
        ? appearToClass
        : enterToClass;
    
      var beforeEnterHook = isAppear
        ? (beforeAppear || beforeEnter)
        : beforeEnter;
      var enterHook = isAppear
        ? (typeof appear === 'function' ? appear : enter)
        : enter;
      var afterEnterHook = isAppear
        ? (afterAppear || afterEnter)
        : afterEnter;
      var enterCancelledHook = isAppear
        ? (appearCancelled || enterCancelled)
        : enterCancelled;
    
      var explicitEnterDuration = toNumber(
        isObject(duration)
          ? duration.enter
          : duration
      );
    
      if ("development" !== 'production' && explicitEnterDuration != null) {
        checkDuration(explicitEnterDuration, 'enter', vnode);
      }
    
      var expectsCSS = css !== false && !isIE9;                     //是否使用 CSS 过渡类 IE9是不支持的
      var userWantsControl = getHookArgumentsLength(enterHook);
    
      var cb = el._enterCb = once(function () {                     //完成后的回调函数
        if (expectsCSS) {
          removeTransitionClass(el, toClass);
          removeTransitionClass(el, activeClass);
        }
        if (cb.cancelled) {
          if (expectsCSS) {
            removeTransitionClass(el, startClass);
          }
          enterCancelledHook && enterCancelledHook(el);
        } else {
          afterEnterHook && afterEnterHook(el);
        }
        el._enterCb = null;
      });
    
      if (!vnode.data.show) {
        // remove pending leave element on enter by injecting an insert hook
        mergeVNodeHook(vnode, 'insert', function () {
          var parent = el.parentNode;
          var pendingNode = parent && parent._pending && parent._pending[vnode.key];
          if (pendingNode &&
            pendingNode.tag === vnode.tag &&
            pendingNode.elm._leaveCb
          ) {
            pendingNode.elm._leaveCb();
          }
          enterHook && enterHook(el, cb);
        });
      }
    
      // start enter transition
      beforeEnterHook && beforeEnterHook(el);               //如果定义了beforeEnterHook钩子函数,则执行它,例子里的beforeenter会执行这里,输出:进入过渡前的事件
      if (expectsCSS) {                                     //如果expectsCSS为true
        addTransitionClass(el, startClass);                     //给el元素新增一个class,名为startClass
        addTransitionClass(el, activeClass);                    //给el元素新增一个class,名为activeClass
        nextFrame(function () {                                 //下次浏览器重绘时
          removeTransitionClass(el, startClass);                  //移除startClass这个class ;因为有设置了activeClass,所以此时就会开始执行动画了
          if (!cb.cancelled) {                                    //如果cb.cancelled为空
            addTransitionClass(el, toClass);                        //添加toClass这个class
            if (!userWantsControl) {
              if (isValidDuration(explicitEnterDuration)) {           //如果用户自定义了动画时间
                setTimeout(cb, explicitEnterDuration);
              } else {
                whenTransitionEnds(el, type, cb);                     //否则执行默认的whenTransitionEnds()函数(等到动画结束后就会执行cb这个回调函数了)
              }
            }
          }
        });
      }
    
      if (vnode.data.show) {
        toggleDisplay && toggleDisplay();
        enterHook && enterHook(el, cb);
      }
    
      if (!expectsCSS && !userWantsControl) {
        cb();
      }
    }

    resolveTransition会根据transitioin里的name属性自动拼凑css名,如下:

    function resolveTransition (def) {        //第7419行 解析transition
      if (!def) {
        return
      }
      /* istanbul ignore else */
      if (typeof def === 'object') {          //如果def是一个对象
        var res = {};
        if (def.css !== false) {                //如果css不等于false
          extend(res, autoCssTransition(def.name || 'v'));    //获取class样式
        }
        extend(res, def);
        return res
      } else if (typeof def === 'string') {
        return autoCssTransition(def)
      }
    }
    
    var autoCssTransition = cached(function (name) {
      return {
        enterClass: (name + "-enter"),
        enterToClass: (name + "-enter-to"),
        enterActiveClass: (name + "-enter-active"),
        leaveClass: (name + "-leave"),
        leaveToClass: (name + "-leave-to"),
        leaveActiveClass: (name + "-leave-active")
      }
    });

    例子里执行到这里时返回的如下:

    回到enter函数,最后会执行whenTransitionEnds函数,如下:

    function whenTransitionEnds (       //第7500行 工具函数,当el元素的动画执行完毕后就去执行cb函数
      el,
      expectedType,
      cb
    ) {
      var ref = getTransitionInfo(el, expectedType);        //获取动画信息
      var type = ref.type;                                  //动画的类型,例如:transition
      var timeout = ref.timeout;                            //动画结束时间
      var propCount = ref.propCount;                        //如果是transition类型的动画,是否有transform动画存在
      if (!type) { return cb() }
      var event = type === TRANSITION ? transitionEndEvent : animationEndEvent;   //如果是transition动画则设置event为transitionend(transition结束事件),否则设置为animationend(animate结束事件)
      var ended = 0;
      var end = function () {
        el.removeEventListener(event, onEnd);
        cb();
      };    
      var onEnd = function (e) {                            //动画结束事件
        if (e.target === el) {
          if (++ended >= propCount) {
            end();                                            //如果所有的动画都执行结束了,则执行end()函数
          }
        }
      };
      setTimeout(function () {
        if (ended < propCount) {
          end();
        }
      }, timeout + 1);
      el.addEventListener(event, onEnd);                    //在el节点上绑定event事件,当动画结束后会执行onEnd函数
    }

    getTransitionInfo用于获取动画的信息,返回一个对象格式,如下:

    function getTransitionInfo (el, expectedType) {     //第7533行 获取el元素上上的transition信息
      var styles = window.getComputedStyle(el);             //获取el元素所有最终使用的CSS属性值
      var transitionDelays = styles[transitionProp + 'Delay'].split(', ');        //transition的延迟时间        ;比如:["0.5s"]
      var transitionDurations = styles[transitionProp + 'Duration'].split(', ');  //动画持续时间
      var transitionTimeout = getTimeout(transitionDelays, transitionDurations);  //获取动画结束的时间
      var animationDelays = styles[animationProp + 'Delay'].split(', ');
      var animationDurations = styles[animationProp + 'Duration'].split(', ');
      var animationTimeout = getTimeout(animationDelays, animationDurations);
    
      var type;
      var timeout = 0;
      var propCount = 0;
      /* istanbul ignore if */
      if (expectedType === TRANSITION) {                  //如果expectedType等于TRANSITION(全局变量,等于字符串:'transition')
        if (transitionTimeout > 0) {
          type = TRANSITION;
          timeout = transitionTimeout;
          propCount = transitionDurations.length;
        }
      } else if (expectedType === ANIMATION) {            //如果是animation动画
        if (animationTimeout > 0) {
          type = ANIMATION;
          timeout = animationTimeout;
          propCount = animationDurations.length;
        }
      } else {
        timeout = Math.max(transitionTimeout, animationTimeout);  //获取两个变量的较大值,保存到timeout里
        type = timeout > 0
          ? transitionTimeout > animationTimeout                  //修正类型
            ? TRANSITION
            : ANIMATION
          : null;
        propCount = type
          ? type === TRANSITION                                   //动画的个数 transition可以一次性指定多个动画的,用,分隔
            ? transitionDurations.length
            : animationDurations.length
          : 0;
      }
      var hasTransform =
        type === TRANSITION &&
        transformRE.test(styles[transitionProp + 'Property']);
      return {                                            //最后返回一个动画相关的对象
        type: type,
        timeout: timeout,
        propCount: propCount,
        hasTransform: hasTransform
      }
    }

    writer by:大沙漠 QQ:22969969

    例子里返回后的对象信息如下:

     回到whenTransitionEnds函数,等到动画结束时就会执行参数3,也就是enter函数内定义的cb局部函数,该函数最终会移除toClass和activeClass,最后执行afterEnter回掉函数。

  • 相关阅读:
    连分数与丢番图方程简介
    利用 random 与 tertools 模块解决概率问题
    Notepad++ 几款实用插件简介,让你的 Notepad++ 如虎添翼
    .net面试问答(大汇总)
    .net反射详解 原文://http://blog.csdn.net/wenyan07/article/details/27882363
    ASP.NET MVC4+EF5(Lambda/Linq)读取数据
    AngularJS+ASP.NET MVC+SignalR实现消息推送
    Asp.net SignalR 实现服务端消息推送到Web端
    C# Socket编程 同步以及异步通信
    C#版 Socket编程(最简单的Socket通信功能)
  • 原文地址:https://www.cnblogs.com/greatdesert/p/11261494.html
Copyright © 2011-2022 走看看