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回掉函數。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM