transition.js 5.7 KB

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