transition.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. var endEvents = sniffEndEvents(),
  2. config = require('./config'),
  3. // batch enter animations so we only force the layout once
  4. Batcher = require('./batcher'),
  5. batcher = new Batcher(),
  6. // exit codes for testing
  7. codes = {
  8. CSS_E : 1,
  9. CSS_L : 2,
  10. JS_E : 3,
  11. JS_L : 4,
  12. CSS_SKIP : -1,
  13. JS_SKIP : -2,
  14. JS_SKIP_E : -3,
  15. JS_SKIP_L : -4,
  16. INIT : -5,
  17. SKIP : -6
  18. }
  19. // force a layout so transition can be triggered
  20. batcher._preFlush = function () {
  21. /* jshint unused: false */
  22. var forceLayout = document.body.clientHeight
  23. }
  24. /**
  25. * stage:
  26. * 1 = enter
  27. * 2 = leave
  28. */
  29. var transition = module.exports = function (el, stage, cb, compiler) {
  30. var changeState = function () {
  31. cb()
  32. compiler.execHook(stage > 0 ? 'attached' : 'detached')
  33. }
  34. if (compiler.init) {
  35. changeState()
  36. return codes.INIT
  37. }
  38. var transitionId = el.vue_trans,
  39. isAnimation = el.vue_anim === ''
  40. if (transitionId) {
  41. return applyTransitionFunctions(
  42. el,
  43. stage,
  44. changeState,
  45. transitionId,
  46. compiler
  47. )
  48. } else if (transitionId === '' || isAnimation) {
  49. return applyTransitionClass(
  50. el,
  51. stage,
  52. changeState,
  53. isAnimation
  54. )
  55. } else {
  56. changeState()
  57. return codes.SKIP
  58. }
  59. }
  60. transition.codes = codes
  61. /**
  62. * Togggle a CSS class to trigger transition
  63. */
  64. function applyTransitionClass (el, stage, changeState, isAnimation) {
  65. if (!endEvents.trans) {
  66. changeState()
  67. return codes.CSS_SKIP
  68. }
  69. // if the browser supports transition,
  70. // it must have classList...
  71. var onEnd, job,
  72. classList = el.classList,
  73. existingCallback = el.vue_trans_cb,
  74. enterClass = config.enterClass,
  75. leaveClass = config.leaveClass,
  76. endEvent = isAnimation
  77. ? endEvents.anim
  78. : endEvents.trans
  79. // cancel unfinished callbacks and jobs
  80. if (existingCallback) {
  81. el.removeEventListener(endEvent, existingCallback)
  82. classList.remove(enterClass)
  83. classList.remove(leaveClass)
  84. el.vue_trans_cb = null
  85. }
  86. if (stage > 0) { // enter
  87. // set to hidden state before appending
  88. if (!isAnimation) {
  89. classList.add(enterClass)
  90. }
  91. // append
  92. changeState()
  93. job = {}
  94. // trigger transition
  95. if (!isAnimation) {
  96. job.execute = function () {
  97. classList.remove(enterClass)
  98. }
  99. } else {
  100. onEnd = function (e) {
  101. if (e.target === el) {
  102. el.removeEventListener(endEvent, onEnd)
  103. el.vue_trans_cb = null
  104. classList.remove(enterClass)
  105. }
  106. }
  107. job.execute = function () {
  108. classList.add(enterClass)
  109. el.addEventListener(endEvent, onEnd)
  110. el.vue_trans_cb = onEnd
  111. }
  112. }
  113. batcher.push(job)
  114. return codes.CSS_E
  115. } else { // leave
  116. if (el.offsetWidth || el.offsetHeight) {
  117. // trigger hide transition
  118. classList.add(leaveClass)
  119. onEnd = function (e) {
  120. if (e.target === el) {
  121. el.removeEventListener(endEvent, onEnd)
  122. el.vue_trans_cb = null
  123. // actually remove node here
  124. changeState()
  125. classList.remove(leaveClass)
  126. }
  127. }
  128. // attach transition end listener
  129. el.addEventListener(endEvent, onEnd)
  130. el.vue_trans_cb = onEnd
  131. } else {
  132. // directly remove invisible elements
  133. changeState()
  134. }
  135. return codes.CSS_L
  136. }
  137. }
  138. function applyTransitionFunctions (el, stage, changeState, functionId, compiler) {
  139. var funcs = compiler.getOption('transitions', functionId)
  140. if (!funcs) {
  141. changeState()
  142. return codes.JS_SKIP
  143. }
  144. var enter = funcs.enter,
  145. leave = funcs.leave
  146. if (stage > 0) { // enter
  147. if (typeof enter !== 'function') {
  148. changeState()
  149. return codes.JS_SKIP_E
  150. }
  151. enter(el, changeState)
  152. return codes.JS_E
  153. } else { // leave
  154. if (typeof leave !== 'function') {
  155. changeState()
  156. return codes.JS_SKIP_L
  157. }
  158. leave(el, changeState)
  159. return codes.JS_L
  160. }
  161. }
  162. /**
  163. * Sniff proper transition end event name
  164. */
  165. function sniffEndEvents () {
  166. var el = document.createElement('vue'),
  167. defaultEvent = 'transitionend',
  168. events = {
  169. 'transition' : defaultEvent,
  170. 'mozTransition' : defaultEvent,
  171. 'webkitTransition' : 'webkitTransitionEnd'
  172. },
  173. ret = {}
  174. for (var name in events) {
  175. if (el.style[name] !== undefined) {
  176. ret.trans = events[name]
  177. break
  178. }
  179. }
  180. ret.anim = el.style.animation === ''
  181. ? 'animationend'
  182. : 'webkitAnimationEnd'
  183. return ret
  184. }