1.模板中transition标签会依据内建的transition组件选项创立组件实例。组件渲染成页面时调用render函数,获取transition标签内的标签对应的节点(插槽),并将transition组件标签上的数据合并到该节点VNode上。
如果先人(包含以后transition标签)都是根标签,且并存在它是transition标签内的标签(插槽)时,间接跳过以后的transition。
mode值为out-in(以后元素先进行过渡,实现之后新元素过渡进入),当transition标签内组件切换时,间接返回空文本节点,来替换旧组件节点,旧节点移除时实现leave动画后,强制更新transition组件,新组件开始插入,并开始enter动画。
mode值为in-out(新元素先进行过渡,实现之后以后元素过渡来到),当transition标签内组件切换时,为旧组件节点增加delayLeave钩子(钩子用来开始节点的leave动画),当新节点实现enter动画后,再执行旧节点的performLeave,开始旧节点的leave动画,最初从document中移除旧节点。
// 内建的transition组件选项var transitionProps = { name: String, appear: Boolean, css: Boolean, mode: String, type: String, enterClass: String, leaveClass: String, enterToClass: String, leaveToClass: String, enterActiveClass: String, leaveActiveClass: String, appearClass: String, appearActiveClass: String, appearToClass: String, duration: [Number, String, Object]};/* transition组件选项 */var Transition = { name: 'transition', props: transitionProps, abstract: true, render: function render (h) {// 获取Vnode节点,h:createElement var this$1 = this; var children = this.$slots.default; if (!children) { return } // filter out text nodes (possible whitespaces) children = children.filter(isNotTextNode);// 过滤文本节点 /* istanbul ignore if */ if (!children.length) { return } // warn multiple elements if (process.env.NODE_ENV !== 'production' && children.length > 1) { 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 (process.env.NODE_ENV !== 'production' && mode && mode !== 'in-out' && mode !== '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. // 如果先人都是根标签,且正好是transition组件标签内的标签(插槽)时,间接跳过以后的transition if (hasParentTransition(this.$vnode)) { return 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) + "-"; 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);// 将transition标签上的数据增加到子标签节点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(isVShowDirective)) { 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 () {// 增加afterLeave回调函数,旧节点动画实现后,再更新组件,从新渲染 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);// 在enter动画实现之后开始调用performLeave,开始leave动画 mergeVNodeHook(data, 'enterCancelled', performLeave);// 在enter动画过程中勾销后开始调用performLeave,开始leave动画 mergeVNodeHook(oldData, 'delayLeave', function (leave) { delayedLeave = leave; });// 更改旧节点的leave动画的执行机会,提早从document移除的工夫 } } return rawChild }};
2.transition的create、activete、reomve办法将会增加到节点钩子函数汇合中,将在节点patch过程中的不同周期调用,在创立dom后、插入到document之前调用_enter办法,在dom从document移除之前调用leave办法。
var transition = inBrowser ? { create: _enter,// 创立dom后调用 activate: _enter,// 创立组件后,且是transition组件根标签节点时 remove: function remove$$1 (vnode, rm) {// 从文档中移除前调用 /* istanbul ignore else */ if (vnode.data.show !== true) { leave(vnode, rm); } else { rm();// 从文档中移除 } }} : {};
3.在enter办法中,创立dom、插入到document之前,增加enter和enterActive类名、调用对应的钩子。插入到文档后移除enter类名,增加enterTo类名。动画实现后,移除enterTo和enterActive类名。
function enter (vnode, toggleDisplay) { var el = vnode.elm; ... var css = data.css;// 是否通过css实现过渡 var type = data.type;// 判断是transition还是animation var enterClass = data.enterClass; var enterToClass = data.enterToClass; var enterActiveClass = data.enterActiveClass; var appearClass = data.appearClass; 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; var transitionNode = activeInstance.$vnode; while (transitionNode && transitionNode.parent) {// 向上查找不是组件根标签的先人节点 context = transitionNode.context; transitionNode = transitionNode.parent; } var isAppear = !context._isMounted || !vnode.isRootInsert; if (isAppear && !appear && appear !== '') { 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 (process.env.NODE_ENV !== 'production' && explicitEnterDuration != null) { checkDuration(explicitEnterDuration, 'enter', vnode); } var expectsCSS = css !== false && !isIE9; var userWantsControl = getHookArgumentsLength(enterHook); var cb = el._enterCb = once(function () {// 动画完结后的回调,第一次调用后生效 if (expectsCSS) { removeTransitionClass(el, toClass);// 移除toClass类名 removeTransitionClass(el, activeClass);// 移除active类名 } if (cb.cancelled) { if (expectsCSS) { removeTransitionClass(el, startClass); } enterCancelledHook && enterCancelledHook(el);// 调用enter过程勾销的钩子 } else { afterEnterHook && afterEnterHook(el);// 调用afterEnter钩子 } 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);// 在插入文档之前调用beforeEnter钩子 if (expectsCSS) { addTransitionClass(el, startClass);// 增加enter类名,在插入到文档后移除 addTransitionClass(el, activeClass);// 增加active类名在 nextFrame(function () {// 下一帧 removeTransitionClass(el, startClass);// 移除enter类名 if (!cb.cancelled) {// 当进入leave状态,cb将会被勾销 addTransitionClass(el, toClass);// 插入到文档后增加enterTo类名 if (!userWantsControl) { if (isValidDuration(explicitEnterDuration)) { setTimeout(cb, explicitEnterDuration); } else { whenTransitionEnds(el, type, cb);// 动画完结后调用cb } } } }); } ...}
4.leave办法中,dom从document移除之前,增加leave和leaveActive类名,以及对应的钩子。在下一帧移除leave类名,增加leaveTo类名。动画实现后移除leaveTo和leaveActive类名,而后dom从document中移除。
function leave (vnode, rm) { var el = vnode.elm; // call enter callback now if (isDef(el._enterCb)) {// 终止enter状态,针对afterEnterHook钩子还没调用的状况,将会触发enterCancelledHook钩子函数 el._enterCb.cancelled = true; el._enterCb(); } var data = resolveTransition(vnode.data.transition); if (isUndef(data) || el.nodeType !== 1) { return rm() } /* istanbul ignore if */ if (isDef(el._leaveCb)) { return } var css = data.css; var type = data.type; var leaveClass = data.leaveClass; var leaveToClass = data.leaveToClass; var leaveActiveClass = data.leaveActiveClass; var beforeLeave = data.beforeLeave; var leave = data.leave; var afterLeave = data.afterLeave; var leaveCancelled = data.leaveCancelled; var delayLeave = data.delayLeave;// mode为in-out时存在 var duration = data.duration; var expectsCSS = css !== false && !isIE9; var userWantsControl = getHookArgumentsLength(leave); var explicitLeaveDuration = toNumber( isObject(duration) ? duration.leave : duration ); if (process.env.NODE_ENV !== 'production' && isDef(explicitLeaveDuration)) { checkDuration(explicitLeaveDuration, 'leave', vnode); } var cb = el._leaveCb = once(function () { if (el.parentNode && el.parentNode._pending) { el.parentNode._pending[vnode.key] = null; } if (expectsCSS) { removeTransitionClass(el, leaveToClass); removeTransitionClass(el, leaveActiveClass); } if (cb.cancelled) { if (expectsCSS) { removeTransitionClass(el, leaveClass); } leaveCancelled && leaveCancelled(el); } else { rm();// 移除DOM元素,会在本次宏工作执行完开始从新渲染 afterLeave && afterLeave(el); } el._leaveCb = null; }); if (delayLeave) { delayLeave(performLeave);// 提早执行performLeave(mode为in-out时,在新节点enter动画实现时执行performLeave,开始leave动画) } else { performLeave(); } function performLeave () { // the delayed leave may have already been cancelled if (cb.cancelled) {// 被勾销了 return } // record leaving element if (!vnode.data.show && el.parentNode) { (el.parentNode._pending || (el.parentNode._pending = {}))[(vnode.key)] = vnode; } beforeLeave && beforeLeave(el);// 调用beforeLeave钩子函数 if (expectsCSS) { addTransitionClass(el, leaveClass);// 增加leave类名 addTransitionClass(el, leaveActiveClass)// 增加leaveActive类名 nextFrame(function () {// removeTransitionClass(el, leaveClass);// 在下一帧移除leave类名 if (!cb.cancelled) { addTransitionClass(el, leaveToClass);// 在下一帧增加leaveToClass类名 if (!userWantsControl) { if (isValidDuration(explicitLeaveDuration)) {// 动画实现后或者设定的持续时间实现后移除leaveTo和leaveActive类名,最初dom从document移除 setTimeout(cb, explicitLeaveDuration);// 在用户设定的持续时间后调用回调函数 } else { whenTransitionEnds(el, type, cb);// 动画完结调用回调函数 } } } }); } leave && leave(el, cb); if (!expectsCSS && !userWantsControl) { cb(); } }}
5.vue通过window.getComputedStyle
获取css设置的动画工夫,element.style 读取的只是元素的内联款式,即写在元素的 style 属性上的款式;而 getComputedStyle 读取的款式是最终款式,包含了内联款式、嵌入款式和内部款式。