Vue.js 源码分析(二十八) 高级应用 transition组件 详解

摘要:
转换组件可以向任何元素和组件添加进入/退出转换,但它只能将转换效果实现到单个组件(多个元素可以使用转换组组件,这将在下一节中讨论)。调用内置组件时,可以传入以下特性:name用于自动生成CSS转换类名。例如,name:“fade”将自动扩展为.fade-enter,外观是否如。淡入激活处于初始阶段

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,这样页面加载时动画就开始了,如下:

Vue.js 源码分析(二十八) 高级应用 transition组件 详解第1张

控制台输出如下:

Vue.js 源码分析(二十八) 高级应用 transition组件 详解第2张

文字从透明到渐显,同时位移也发生了变化,我们点击按钮时又会触发隐藏,继续点击,又会显示,这是因为我们在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>

渲染的页面如下:

Vue.js 源码分析(二十八) 高级应用 transition组件 详解第3张

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

Vue.js 源码分析(二十八) 高级应用 transition组件 详解第4张

这个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就真的隐藏了,变为了一个注释节点,如下:

Vue.js 源码分析(二十八) 高级应用 transition组件 详解第5张

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

Vue.js 源码分析(二十八) 高级应用 transition组件 详解第6张

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

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

 源码分析


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

Vue.js 源码分析(二十八) 高级应用 transition组件 详解第7张

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   + (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组件执行到返回的值如下:

Vue.js 源码分析(二十八) 高级应用 transition组件 详解第8张

也就是说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")
  }
});

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

Vue.js 源码分析(二十八) 高级应用 transition组件 详解第9张

回到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

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

Vue.js 源码分析(二十八) 高级应用 transition组件 详解第10张

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

免责声明:文章转载自《Vue.js 源码分析(二十八) 高级应用 transition组件 详解》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇在Ubuntu下安装deb包需要使用dpkg命令c# 序列化效率比拼下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

【转】CSS浏览器兼容性与解析问题终极归纳

1.怪异模式问题:漏写DTD声明,Firefox仍然会按照标准模式来解析网页,但在IE中会触发怪异模式。为避免怪异模式给我们带来不必要的麻烦,最好养成书写DTD声明的好习惯。 2.IE6双边距问题:在IE6下,如果对元素设置了浮动,同时又设置了margin-left或margin-right,margin值会加倍。例如: HTML: <div cla...

# vue 使用 cesium 接入 gltf 模型

vue 使用 cesium 接入 gltf 模型 这个其实说简单也简单,但是说复杂也不容易搞。尤其是转化成vue语法或者是在vue项目接入的时候会有些许的坑,我在接入的时候也是有很多问题,好在最后把模型加进去了,在这里稍微整理一下,然后这篇博文的代码都是我自己实现成功的,如果需要的话可以相互学习一下。第一次整,也许不是最优编程,交流嘛。 cesium中文文...

vue单页面项目返回上一页无效,链接变化了,但是页面没有变化

在最近的项目中,返回上一页没有效果,经过好久的排查才发现问题,是路由守卫写法不规范导致。 在项目中用路由守卫做了登录拦截,没登录的跳转到登录页面。页面跳转和拦截都没问题,但是返回上一页就不行了,也没有报错。 代码贴上来 router.beforeEach((to, from, next) =>{ if (to.meta.loginChec...

vue发布IIS踩坑记

步骤一:复制文件 把build之后的文件(dist文件夹)拷贝到IIS存放网站文件的目录 步骤二:在IIS中新建站点  在"网站"执行鼠标右键,选择添加网站。 网站名称:按照用途或者项目起名即可 应用程序池:部署前端项目,这个可以忽略,任意选择即可 内容目录:网站存放的路径,最好以英文命名 端口:端口自己设置即可 步骤三:访问网站  访问网站可以在II...

Vue 使用 Antd 简单实现左侧菜单栏和面包屑功能

参考: https://www.cnblogs.com/wjw1014/p/13925969.html    路由调用 import Hello from '@/Home/Hello' Vue.use(Router) export default new Router({ routes: [ { path: "/2",...

VUE动态修改titile的三种方法

第一种:适用于在已经定义好title的情况下,例如首页,关于页等等 1.1 main.js const defaultTitle = '默认 title' router.beforeEach((to, from, next) => { document.title = to.meta.title ? to.meta.title : defaultT...