transition.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import { addClass, removeClass } from '../class-util'
  2. import { isIE9, inBrowser, cached } from '../../util/index'
  3. const TRANSITION = 'transition'
  4. const ANIMATION = 'animation'
  5. // Transition property/event sniffing
  6. let transitionProp
  7. let transitionEndEvent
  8. let animationProp
  9. let animationEndEvent
  10. if (inBrowser && !isIE9) {
  11. const isWebkitTrans =
  12. window.ontransitionend === undefined &&
  13. window.onwebkittransitionend !== undefined
  14. const isWebkitAnim =
  15. window.onanimationend === undefined &&
  16. window.onwebkitanimationend !== undefined
  17. transitionProp = isWebkitTrans ? 'WebkitTransition' : 'transition'
  18. transitionEndEvent = isWebkitTrans ? 'webkitTransitionEnd' : 'transitionend'
  19. animationProp = isWebkitAnim ? 'WebkitAnimation' : 'animation'
  20. animationEndEvent = isWebkitAnim ? 'webkitAnimationEnd' : 'animationend'
  21. }
  22. const raf = (inBrowser && window.requestAnimationFrame) || setTimeout
  23. function nextFrame (fn) {
  24. raf(() => {
  25. raf(fn)
  26. })
  27. }
  28. function beforeEnter (_, vnode) {
  29. // if this is a component root node and the compoennt's
  30. // parent container node also has transition, skip.
  31. if (vnode.parent && vnode.parent.data.transition) {
  32. return
  33. }
  34. const el = vnode.elm
  35. const data = vnode.data.transition
  36. if (data == null) {
  37. return
  38. }
  39. const {
  40. enterClass,
  41. enterActiveClass,
  42. beforeEnter,
  43. enter,
  44. afterEnter,
  45. enterCancelled
  46. } = detectAuto(data)
  47. const userWantsControl = enter && enter.length > 1
  48. const cb = el._enterCb = () => {
  49. // ensure only called once
  50. if (cb.called) {
  51. return
  52. }
  53. cb.called = true
  54. if (enterActiveClass) {
  55. removeTransitionClass(el, enterActiveClass)
  56. }
  57. if (cb.cancelled) {
  58. enterCancelled && enterCancelled(el)
  59. } else {
  60. afterEnter && afterEnter(el)
  61. }
  62. el._enterCb = null
  63. }
  64. beforeEnter && beforeEnter(el)
  65. if (enterClass) {
  66. addTransitionClass(el, enterClass)
  67. nextFrame(() => {
  68. removeTransitionClass(el, enterClass)
  69. })
  70. }
  71. if (enterActiveClass) {
  72. nextFrame(() => {
  73. addTransitionClass(el, enterActiveClass)
  74. if (!userWantsControl) {
  75. whenTransitionEnds(el, cb)
  76. }
  77. })
  78. }
  79. enter && enter(el, cb)
  80. if (!enterActiveClass && !userWantsControl) {
  81. cb()
  82. }
  83. }
  84. function onLeave (vnode, rm) {
  85. // if this is a component root node and the compoennt's
  86. // parent container node also has transition, skip.
  87. if (vnode.parent && vnode.parent.data.transition) {
  88. return
  89. }
  90. const el = vnode.elm
  91. // call enter callback now
  92. if (el._enterCb) {
  93. el._enterCb.cancelled = true
  94. el._enterCb()
  95. }
  96. const data = vnode.data.transition
  97. if (data == null) {
  98. return rm()
  99. }
  100. const {
  101. leaveClass,
  102. leaveActiveClass,
  103. beforeLeave,
  104. leave,
  105. afterLeave
  106. } = detectAuto(data)
  107. const userWantsControl = leave && leave.length > 1
  108. const cb = () => {
  109. rm()
  110. afterLeave && afterLeave(el)
  111. }
  112. beforeLeave && beforeLeave(el)
  113. if (leaveClass) {
  114. addTransitionClass(el, leaveClass)
  115. nextFrame(() => {
  116. removeTransitionClass(el, leaveClass)
  117. })
  118. }
  119. if (leaveActiveClass) {
  120. nextFrame(() => {
  121. addTransitionClass(el, leaveActiveClass)
  122. if (!userWantsControl) {
  123. whenTransitionEnds(el, cb)
  124. }
  125. })
  126. }
  127. leave && leave(el, cb)
  128. if (!leaveActiveClass && !userWantsControl) {
  129. cb()
  130. }
  131. }
  132. function detectAuto (data) {
  133. return typeof data === 'string'
  134. ? autoCssTransition(data)
  135. : data
  136. }
  137. const autoCssTransition = cached(name => {
  138. name = name || 'v'
  139. return {
  140. enterClass: `${name}-enter`,
  141. leaveClass: `${name}-leave`,
  142. enterActiveClass: `${name}-enter-active`,
  143. leaveActiveClass: `${name}-leave-active`
  144. }
  145. })
  146. function addTransitionClass (el, cls) {
  147. (el._transitionClasses || (el._transitionClasses = [])).push(cls)
  148. addClass(el, cls)
  149. }
  150. function removeTransitionClass (el, cls) {
  151. el._transitionClasses.$remove(cls)
  152. removeClass(el, cls)
  153. }
  154. function whenTransitionEnds (el, cb) {
  155. const { type, timeout, propCount } = getTransitionInfo(el)
  156. if (!type) return cb()
  157. const event = type === TRANSITION ? transitionEndEvent : animationEndEvent
  158. let ended = 0
  159. const end = () => {
  160. el.removeEventListener(event, onEnd)
  161. cb()
  162. }
  163. const onEnd = () => {
  164. if (++ended >= propCount) {
  165. end()
  166. }
  167. }
  168. setTimeout(() => {
  169. if (ended < propCount) {
  170. end()
  171. }
  172. }, timeout)
  173. el.addEventListener(event, onEnd)
  174. }
  175. function getTransitionInfo (el) {
  176. const styles = window.getComputedStyle(el)
  177. // 1. determine the maximum duration (timeout)
  178. const transitioneDelays = styles[transitionProp + 'Delay'].split(', ')
  179. const transitionDurations = styles[transitionProp + 'Duration'].split(', ')
  180. const animationDelays = styles[animationProp + 'Delay'].split(', ')
  181. const animationDurations = styles[animationProp + 'Duration'].split(', ')
  182. const transitionTimeout = getTimeout(transitioneDelays, transitionDurations)
  183. const animationTimeout = getTimeout(animationDelays, animationDurations)
  184. const timeout = Math.max(transitionTimeout, animationTimeout)
  185. const type = timeout > 0
  186. ? transitionTimeout > animationTimeout
  187. ? TRANSITION
  188. : ANIMATION
  189. : null
  190. const propCount = type
  191. ? type === TRANSITION
  192. ? transitionDurations.length
  193. : animationDurations.length
  194. : 0
  195. return {
  196. type,
  197. timeout,
  198. propCount
  199. }
  200. }
  201. function getTimeout (delays, durations) {
  202. return Math.max.apply(null, durations.map((d, i) => {
  203. return toMs(d) + toMs(delays[i])
  204. }))
  205. }
  206. function toMs (s) {
  207. return Number(s.slice(0, -1)) * 1000
  208. }
  209. export default !transitionEndEvent ? {} : {
  210. create: beforeEnter,
  211. remove: onLeave
  212. }