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
- }
- }
例子里返回后的对象信息如下:

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