经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » HTML/CSS » HTML5 » 查看文章
Vue.js 源码分析(二十八) 高级应用 transition组件 详解
来源:cnblogs  作者:大沙漠  时间:2019/7/30 9:36:33  对本文有异议

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

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Document</title>
  6. <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
  7. </head>
  8. <style>
  9. .fade-enter,.fade-leave-to{background: #f00;transform:translateY(20px);} /*.fade-enter和.fade-leave-to一般写在一起,当然也可以分开*/
  10. .fade-enter-active,.fade-leave-to{transition:all 1s linear 500ms;}
  11. </style>
  12. <body>
  13. <div id="app">
  14. <button @click="show=!show">按钮</button>
  15. <transition name="fade" :appear="true" @before-enter="beforeenter" @enter="enter" @after-enter="afterenter" @before-leave="beforeleave" @leave="leave" @after-leave="afterleave">
  16. <p v-if="show">你好</p>
  17. </transition>
  18. </div>
  19. <script>
  20. Vue.config.productionTip=false;
  21. Vue.config.devtools=false;
  22. var app = new Vue({
  23. el:"#app",
  24. data:{
  25. show:true
  26. },
  27. methods:{
  28. beforeenter(){console.log('进入过渡前的事件')},
  29. enter(){console.log('进入过渡开始的事件')},
  30. afterenter(){console.log('进入过渡结束的事件')},
  31. beforeleave(){console.log('离开过渡前的事件')},
  32. leave(){console.log('离开过渡开始的事件')},
  33. afterleave(){console.log('离开过渡结束的事件')}
  34. }
  35. })
  36. </script>
  37. </body>
  38. </html>

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

控制台输出如下:

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

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

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

 

用原生DOM模拟transition组件


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

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Document</title>
  6. </head>
  7. <style>
  8. .trans{transition:all 2s linear;}
  9. .start{transform:translatex(100px);opacity: 0;}
  10. </style>
  11. <body>
  12. <div id="con">
  13. <button name="show">显式</button>
  14. <button name="hide">隐藏</button>
  15. </div>
  16. <p id="p">Hello Vue!</p>
  17. <script>
  18. var p = document.getElementsByTagName('p')[0];
  19. document.getElementById('con').addEventListener('click',function(event){
  20. switch(event.target.name){
  21. case "show":
  22. p.style.display="block";
  23. p.classList.add('trans');
  24. p.classList.remove('start')
  25. break;
  26. case "hide":
  27. p.classList.add('trans')
  28. p.classList.add('start')
  29. break;
  30. }
  31. })
  32. </script>
  33. </body>
  34. </html>

渲染的页面如下:

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

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

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

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

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Document</title>
  6. </head>
  7. <style>
  8. .trans{transition:all 2s linear;}
  9. .start{transform:translatex(100px);opacity: 0;}
  10. </style>
  11. <body>
  12. <div id="con">
  13. <button name="show">显式</button>
  14. <button name="hide">隐藏</button>
  15. </div>
  16. <p id="p">Hello Vue!</p>
  17. <script>
  18. var p = document.getElementsByTagName('p')[0],
  19. tid = null,
  20. pDom = null,
  21. CommentDom = document.createComment("");
  22. document.getElementById('con').addEventListener('click',function(event){
  23. switch(event.target.name){
  24. case "show":
  25. CommentDom.parentNode.replaceChild(p,CommentDom)
  26. setTimeout(function(){p.classList.remove('start')},10)
  27. ModifyClass(1)
  28. break;
  29. case "hide":
  30. p.classList.add('trans')
  31. p.classList.add('start')
  32. ModifyClass(0)
  33. break;
  34. }
  35. })
  36. function ModifyClass(n){ //s=1:显式过程 s=0:隐藏过程
  37. var styles = window.getComputedStyle(p);
  38. var transitionDelays = styles['transitionDelay'].split(', '); //transition的延迟时间 ;比如:["0.5s"]
  39. var transitionDurations = styles['transitionDuration'].split(', '); //transition的动画持续时间 ;比如:"1s"
  40. var transitionTimeout = getTimeout(transitionDelays, transitionDurations); //transition的获取动画结束的时间,单位ms,比如:1500
  41. tid && clearTimeout(tid);
  42. tid=setTimeout(function(){
  43. if(n){ //如果是显式
  44. p.classList.remove('trans')
  45. p.removeAttribute('class');
  46. }else{ //如果是隐藏
  47. p.parentNode.replaceChild(CommentDom,p);
  48. }
  49. },transitionTimeout)
  50. }
  51. function getTimeout(delays, durations) { //从Vue源码里拷贝出来的代码的,获取动画完成的总时间,返回ms格式
  52. while (delays.length < durations.length) {
  53. delays = delays.concat(delays);
  54. }
  55. return Math.max.apply(null, durations.map(function (d, i) {
  56. return toMs(d) + toMs(delays[i])
  57. }))
  58. }
  59. function toMs(s) {
  60. return Number(s.slice(0, -1)) * 1000
  61. }
  62. </script>
  63.  
  64. </body>
  65. </html>

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

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

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

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

 

 源码分析


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

Transition组件的格式为:

  1. var Transition = { //第8012行 transition组件的定义
  2. name: 'transition',
  3. props: transitionProps,
  4. abstract: true,
  5. render: function render (h) {
  6. /**/
  7. }
  8. }

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

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

  1. render: function render (h) { //第8217行 transition组件的render函数,并没有template模板,初始化或更新都会执行到这里
  2. var this$1 = this;
  3. var children = this.$slots.default;
  4. if (!children) {
  5. return
  6. }
  7. // filter out text nodes (possible whitespaces)
  8. children = children.filter(function (c) { return c.tag || isAsyncPlaceholder(c); });
  9. /* istanbul ignore if */
  10. if (!children.length) { //获取子节点
  11. return //如果没有子节点,则直接返回
  12. }
  13. // warn multiple elements
  14. if ("development" !== 'production' && children.length > 1) { //如果过滤掉空白节点后,children还是不存在,则直接返回
  15. warn(
  16. '<transition> can only be used on a single element. Use ' +
  17. '<transition-group> for lists.',
  18. this.$parent
  19. );
  20. }
  21. var mode = this.mode; //获取模式
  22.  
  23. // warn invalid mode
  24. if ("development" !== 'production' &&
  25. mode && mode !== 'in-out' && mode !== 'out-in' //检查mode是否规范只能是in-out或out-in
  26. ) {
  27. warn(
  28. 'invalid <transition> mode: ' + mode,
  29. this.$parent
  30. );
  31. }
  32. var rawChild = children[0]; //获取所有子节点
  33.  
  34. // if this is a component root node and the component's
  35. // parent container node also has transition, skip.
  36. if (hasParentTransition(this.$vnode)) { //如果当前的transition是根组件,且调用该组件的时候外层又套了一个transition
  37. return rawChild //则直接返回rawChild
  38. }
  39. // apply transition data to child
  40. // use getRealChild() to ignore abstract components e.g. keep-alive
  41. var child = getRealChild(rawChild);
  42. /* istanbul ignore if */
  43. if (!child) {
  44. return rawChild
  45. }
  46. if (this._leaving) {
  47. return placeholder(h, rawChild)
  48. }
  49. // ensure a key that is unique to the vnode type and to this transition
  50. // component instance. This key will be used to remove pending leaving nodes
  51. // during entering.
  52. var id = "__transition-" + (this._uid) + "-"; //拼凑key,比如:__transition-1 ;this._uid是transition组件实例的_uid,在_init初始化时定义的
  53. child.key = child.key == null
  54. ? child.isComment
  55. ? id + 'comment'
  56. : id + child.tag
  57. : isPrimitive(child.key)
  58. ? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key)
  59. : child.key;
  60. var data = (child.data || (child.data = {})).transition = extractTransitionData(this); //获取组件上的props和自定义事件,保存到child.data.transition里
  61. var oldRawChild = this._vnode;
  62. var oldChild = getRealChild(oldRawChild);
  63. // mark v-show
  64. // so that the transition module can hand over the control to the directive
  65. if (child.data.directives && child.data.directives.some(function (d) { return d.name === 'show'; })) { //如果child带有一个v-show指令
  66. child.data.show = true; //则给child.data新增一个show属性,值为true
  67. }
  68. if (
  69. oldChild &&
  70. oldChild.data &&
  71. !isSameChild(child, oldChild) &&
  72. !isAsyncPlaceholder(oldChild) &&
  73. // #6687 component root is a comment node
  74. !(oldChild.componentInstance && oldChild.componentInstance._vnode.isComment) //这里是更新组件,且子组件改变之后的逻辑
  75. ) {
  76. // replace old child transition data with fresh one
  77. // important for dynamic transitions!
  78. var oldData = oldChild.data.transition = extend({}, data);
  79. // handle transition mode
  80. if (mode === 'out-in') {
  81. // return placeholder node and queue update when leave finishes
  82. this._leaving = true;
  83. mergeVNodeHook(oldData, 'afterLeave', function () {
  84. this$1._leaving = false;
  85. this$1.$forceUpdate();
  86. });
  87. return placeholder(h, rawChild)
  88. } else if (mode === 'in-out') {
  89. if (isAsyncPlaceholder(child)) {
  90. return oldRawChild
  91. }
  92. var delayedLeave;
  93. var performLeave = function () { delayedLeave(); };
  94. mergeVNodeHook(data, 'afterEnter', performLeave);
  95. mergeVNodeHook(data, 'enterCancelled', performLeave);
  96. mergeVNodeHook(oldData, 'delayLeave', function (leave) { delayedLeave = leave; });
  97. }
  98. }
  99. return rawChild //返回DOM节点
  100. }

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

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

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

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

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

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

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

  1. function enter (vnode, toggleDisplay) { //第7599行 进入动画的回调函数
  2. var el = vnode.elm;
  3. // call leave callback now
  4. if (isDef(el._leaveCb)) { //如果el._leaveCb存在,则执行它,离开过渡未执行完时如果重新触发了进入过渡,则执行到这里
  5. el._leaveCb.cancelled = true;
  6. el._leaveCb();
  7. }
  8. var data = resolveTransition(vnode.data.transition); //调用resolveTransition解析vnode.data.transition里的css属性
  9. if (isUndef(data)) {
  10. return
  11. }
  12. /* istanbul ignore if */
  13. if (isDef(el._enterCb) || el.nodeType !== 1) {
  14. return
  15. }
  16. var css = data.css; //是否使用 CSS 过渡类
  17. var type = data.type; //过滤类型,可以是transition或animation 可以为空,Vue内部会自动检测
  18. var enterClass = data.enterClass; //获取进入过渡是的起始、结束和过渡时的状态对应的class
  19. var enterToClass = data.enterToClass;
  20. var enterActiveClass = data.enterActiveClass;
  21. var appearClass = data.appearClass; //获取初次渲染时的过渡,分别是起始、结束和过渡时的状态对应的class
  22. var appearToClass = data.appearToClass;
  23. var appearActiveClass = data.appearActiveClass;
  24. var beforeEnter = data.beforeEnter; //进入过渡前的事件,以下都是相关事件
  25. var enter = data.enter;
  26. var afterEnter = data.afterEnter;
  27. var enterCancelled = data.enterCancelled;
  28. var beforeAppear = data.beforeAppear;
  29. var appear = data.appear;
  30. var afterAppear = data.afterAppear;
  31. var appearCancelled = data.appearCancelled;
  32. var duration = data.duration;
  33. // activeInstance will always be the <transition> component managing this
  34. // transition. One edge case to check is when the <transition> is placed
  35. // as the root node of a child component. In that case we need to check
  36. // <transition>'s parent for appear check.
  37. var context = activeInstance; //当前transition组件的Vue实例vm
  38. var transitionNode = activeInstance.$vnode; //占位符VNode
  39. while (transitionNode && transitionNode.parent) { //如果transitoin组件是作为根节点的
  40. transitionNode = transitionNode.parent; //则修正transitionNode为它的parent
  41. context = transitionNode.context; //修正context为对应的parent的context
  42. }
  43. var isAppear = !context._isMounted || !vnode.isRootInsert; //当前是否还未初始化 如果transition组件还没有挂载,则设置isAppear为true
  44.  
  45. if (isAppear && !appear && appear !== '') { //如果appear为false(当前是初始化),且appear为false(即初始渲染时不使用过渡),或不存在
  46. return //则直接返回,不做处理
  47. }
  48. var startClass = isAppear && appearClass //进入过渡的起始状态
  49. ? appearClass
  50. : enterClass;
  51. var activeClass = isAppear && appearActiveClass //进入过渡时的状态
  52. ? appearActiveClass
  53. : enterActiveClass;
  54. var toClass = isAppear && appearToClass //进入过渡的结束状态
  55. ? appearToClass
  56. : enterToClass;
  57. var beforeEnterHook = isAppear
  58. ? (beforeAppear || beforeEnter)
  59. : beforeEnter;
  60. var enterHook = isAppear
  61. ? (typeof appear === 'function' ? appear : enter)
  62. : enter;
  63. var afterEnterHook = isAppear
  64. ? (afterAppear || afterEnter)
  65. : afterEnter;
  66. var enterCancelledHook = isAppear
  67. ? (appearCancelled || enterCancelled)
  68. : enterCancelled;
  69. var explicitEnterDuration = toNumber(
  70. isObject(duration)
  71. ? duration.enter
  72. : duration
  73. );
  74. if ("development" !== 'production' && explicitEnterDuration != null) {
  75. checkDuration(explicitEnterDuration, 'enter', vnode);
  76. }
  77. var expectsCSS = css !== false && !isIE9; //是否使用 CSS 过渡类 IE9是不支持的
  78. var userWantsControl = getHookArgumentsLength(enterHook);
  79. var cb = el._enterCb = once(function () { //完成后的回调函数
  80. if (expectsCSS) {
  81. removeTransitionClass(el, toClass);
  82. removeTransitionClass(el, activeClass);
  83. }
  84. if (cb.cancelled) {
  85. if (expectsCSS) {
  86. removeTransitionClass(el, startClass);
  87. }
  88. enterCancelledHook && enterCancelledHook(el);
  89. } else {
  90. afterEnterHook && afterEnterHook(el);
  91. }
  92. el._enterCb = null;
  93. });
  94. if (!vnode.data.show) {
  95. // remove pending leave element on enter by injecting an insert hook
  96. mergeVNodeHook(vnode, 'insert', function () {
  97. var parent = el.parentNode;
  98. var pendingNode = parent && parent._pending && parent._pending[vnode.key];
  99. if (pendingNode &&
  100. pendingNode.tag === vnode.tag &&
  101. pendingNode.elm._leaveCb
  102. ) {
  103. pendingNode.elm._leaveCb();
  104. }
  105. enterHook && enterHook(el, cb);
  106. });
  107. }
  108. // start enter transition
  109. beforeEnterHook && beforeEnterHook(el); //如果定义了beforeEnterHook钩子函数,则执行它,例子里的beforeenter会执行这里,输出:进入过渡前的事件
  110. if (expectsCSS) { //如果expectsCSS为true
  111. addTransitionClass(el, startClass); //给el元素新增一个class,名为startClass
  112. addTransitionClass(el, activeClass); //给el元素新增一个class,名为activeClass
  113. nextFrame(function () { //下次浏览器重绘时
  114. removeTransitionClass(el, startClass); //移除startClass这个class ;因为有设置了activeClass,所以此时就会开始执行动画了
  115. if (!cb.cancelled) { //如果cb.cancelled为空
  116. addTransitionClass(el, toClass); //添加toClass这个class
  117. if (!userWantsControl) {
  118. if (isValidDuration(explicitEnterDuration)) { //如果用户自定义了动画时间
  119. setTimeout(cb, explicitEnterDuration);
  120. } else {
  121. whenTransitionEnds(el, type, cb); //否则执行默认的whenTransitionEnds()函数(等到动画结束后就会执行cb这个回调函数了)
  122. }
  123. }
  124. }
  125. });
  126. }
  127. if (vnode.data.show) {
  128. toggleDisplay && toggleDisplay();
  129. enterHook && enterHook(el, cb);
  130. }
  131. if (!expectsCSS && !userWantsControl) {
  132. cb();
  133. }
  134. }

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

  1. function resolveTransition (def) { //第7419行 解析transition
  2. if (!def) {
  3. return
  4. }
  5. /* istanbul ignore else */
  6. if (typeof def === 'object') { //如果def是一个对象
  7. var res = {};
  8. if (def.css !== false) { //如果css不等于false
  9. extend(res, autoCssTransition(def.name || 'v')); //获取class样式
  10. }
  11. extend(res, def);
  12. return res
  13. } else if (typeof def === 'string') {
  14. return autoCssTransition(def)
  15. }
  16. }
  17. var autoCssTransition = cached(function (name) {
  18. return {
  19. enterClass: (name + "-enter"),
  20. enterToClass: (name + "-enter-to"),
  21. enterActiveClass: (name + "-enter-active"),
  22. leaveClass: (name + "-leave"),
  23. leaveToClass: (name + "-leave-to"),
  24. leaveActiveClass: (name + "-leave-active")
  25. }
  26. });

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

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

 

  1. function whenTransitionEnds ( //第7500行 工具函数,当el元素的动画执行完毕后就去执行cb函数
  2. el,
  3. expectedType,
  4. cb
  5. ) {
  6. var ref = getTransitionInfo(el, expectedType); //获取动画信息
  7. var type = ref.type; //动画的类型,例如:transition
  8. var timeout = ref.timeout; //动画结束时间
  9. var propCount = ref.propCount; //如果是transition类型的动画,是否有transform动画存在
  10. if (!type) { return cb() }
  11. var event = type === TRANSITION ? transitionEndEvent : animationEndEvent; //如果是transition动画则设置event为transitionend(transition结束事件),否则设置为animationend(animate结束事件)
  12. var ended = 0;
  13. var end = function () {
  14. el.removeEventListener(event, onEnd);
  15. cb();
  16. };
  17. var onEnd = function (e) { //动画结束事件
  18. if (e.target === el) {
  19. if (++ended >= propCount) {
  20. end(); //如果所有的动画都执行结束了,则执行end()函数
  21. }
  22. }
  23. };
  24. setTimeout(function () {
  25. if (ended < propCount) {
  26. end();
  27. }
  28. }, timeout + 1);
  29. el.addEventListener(event, onEnd); //在el节点上绑定event事件,当动画结束后会执行onEnd函数
  30. }

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

  1. function getTransitionInfo (el, expectedType) { //第7533行 获取el元素上上的transition信息
  2. var styles = window.getComputedStyle(el); //获取el元素所有最终使用的CSS属性值
  3. var transitionDelays = styles[transitionProp + 'Delay'].split(', '); //transition的延迟时间 ;比如:["0.5s"]
  4. var transitionDurations = styles[transitionProp + 'Duration'].split(', '); //动画持续时间
  5. var transitionTimeout = getTimeout(transitionDelays, transitionDurations); //获取动画结束的时间
  6. var animationDelays = styles[animationProp + 'Delay'].split(', ');
  7. var animationDurations = styles[animationProp + 'Duration'].split(', ');
  8. var animationTimeout = getTimeout(animationDelays, animationDurations);
  9. var type;
  10. var timeout = 0;
  11. var propCount = 0;
  12. /* istanbul ignore if */
  13. if (expectedType === TRANSITION) { //如果expectedType等于TRANSITION(全局变量,等于字符串:'transition')
  14. if (transitionTimeout > 0) {
  15. type = TRANSITION;
  16. timeout = transitionTimeout;
  17. propCount = transitionDurations.length;
  18. }
  19. } else if (expectedType === ANIMATION) { //如果是animation动画
  20. if (animationTimeout > 0) {
  21. type = ANIMATION;
  22. timeout = animationTimeout;
  23. propCount = animationDurations.length;
  24. }
  25. } else {
  26. timeout = Math.max(transitionTimeout, animationTimeout); //获取两个变量的较大值,保存到timeout里
  27. type = timeout > 0
  28. ? transitionTimeout > animationTimeout //修正类型
  29. ? TRANSITION
  30. : ANIMATION
  31. : null;
  32. propCount = type
  33. ? type === TRANSITION //动画的个数 transition可以一次性指定多个动画的,用,分隔
  34. ? transitionDurations.length
  35. : animationDurations.length
  36. : 0;
  37. }
  38. var hasTransform =
  39. type === TRANSITION &&
  40. transformRE.test(styles[transitionProp + 'Property']);
  41. return { //最后返回一个动画相关的对象
  42. type: type,
  43. timeout: timeout,
  44. propCount: propCount,
  45. hasTransform: hasTransform
  46. }
  47. }

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

 

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

 

原文链接:http://www.cnblogs.com/greatdesert/p/11261494.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号