transition.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. /* @flow */
  2. import { inBrowser, isIE9 } from 'core/util/index'
  3. import { cached, extend } from 'shared/util'
  4. import { mergeVNodeHook } from 'core/vdom/helpers/index'
  5. import { activeInstance } from 'core/instance/lifecycle'
  6. import {
  7. nextFrame,
  8. addTransitionClass,
  9. removeTransitionClass,
  10. whenTransitionEnds
  11. } from '../transition-util'
  12. export function enter (vnode: VNodeWithData, toggleDisplay: ?() => void) {
  13. const el: any = vnode.elm
  14. // call leave callback now
  15. if (el._leaveCb) {
  16. el._leaveCb.cancelled = true
  17. el._leaveCb()
  18. }
  19. const data = resolveTransition(vnode.data.transition)
  20. if (!data) {
  21. return
  22. }
  23. /* istanbul ignore if */
  24. if (el._enterCb || el.nodeType !== 1) {
  25. return
  26. }
  27. const {
  28. css,
  29. type,
  30. enterClass,
  31. enterToClass,
  32. enterActiveClass,
  33. appearClass,
  34. appearToClass,
  35. appearActiveClass,
  36. beforeEnter,
  37. enter,
  38. afterEnter,
  39. enterCancelled,
  40. beforeAppear,
  41. appear,
  42. afterAppear,
  43. appearCancelled
  44. } = data
  45. // activeInstance will always be the <transition> component managing this
  46. // transition. One edge case to check is when the <transition> is placed
  47. // as the root node of a child component. In that case we need to check
  48. // <transition>'s parent for appear check.
  49. let context = activeInstance
  50. let transitionNode = activeInstance.$vnode
  51. while (transitionNode && transitionNode.parent) {
  52. transitionNode = transitionNode.parent
  53. context = transitionNode.context
  54. }
  55. const isAppear = !context._isMounted || !vnode.isRootInsert
  56. if (isAppear && !appear && appear !== '') {
  57. return
  58. }
  59. const startClass = isAppear ? appearClass : enterClass
  60. const activeClass = isAppear ? appearActiveClass : enterActiveClass
  61. const toClass = isAppear ? appearToClass : enterToClass
  62. const beforeEnterHook = isAppear ? (beforeAppear || beforeEnter) : beforeEnter
  63. const enterHook = isAppear ? (typeof appear === 'function' ? appear : enter) : enter
  64. const afterEnterHook = isAppear ? (afterAppear || afterEnter) : afterEnter
  65. const enterCancelledHook = isAppear ? (appearCancelled || enterCancelled) : enterCancelled
  66. const expectsCSS = css !== false && !isIE9
  67. const userWantsControl =
  68. enterHook &&
  69. // enterHook may be a bound method which exposes
  70. // the length of original fn as _length
  71. (enterHook._length || enterHook.length) > 1
  72. const cb = el._enterCb = once(() => {
  73. if (expectsCSS) {
  74. removeTransitionClass(el, toClass)
  75. removeTransitionClass(el, activeClass)
  76. }
  77. if (cb.cancelled) {
  78. if (expectsCSS) {
  79. removeTransitionClass(el, startClass)
  80. }
  81. enterCancelledHook && enterCancelledHook(el)
  82. } else {
  83. afterEnterHook && afterEnterHook(el)
  84. }
  85. el._enterCb = null
  86. })
  87. if (!vnode.data.show) {
  88. // remove pending leave element on enter by injecting an insert hook
  89. mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'insert', () => {
  90. const parent = el.parentNode
  91. const pendingNode = parent && parent._pending && parent._pending[vnode.key]
  92. if (pendingNode &&
  93. pendingNode.tag === vnode.tag &&
  94. pendingNode.elm._leaveCb) {
  95. pendingNode.elm._leaveCb()
  96. }
  97. enterHook && enterHook(el, cb)
  98. }, 'transition-insert')
  99. }
  100. // start enter transition
  101. beforeEnterHook && beforeEnterHook(el)
  102. if (expectsCSS) {
  103. addTransitionClass(el, startClass)
  104. addTransitionClass(el, activeClass)
  105. nextFrame(() => {
  106. addTransitionClass(el, toClass)
  107. removeTransitionClass(el, startClass)
  108. if (!cb.cancelled && !userWantsControl) {
  109. whenTransitionEnds(el, type, cb)
  110. }
  111. })
  112. }
  113. if (vnode.data.show) {
  114. toggleDisplay && toggleDisplay()
  115. enterHook && enterHook(el, cb)
  116. }
  117. if (!expectsCSS && !userWantsControl) {
  118. cb()
  119. }
  120. }
  121. export function leave (vnode: VNodeWithData, rm: Function) {
  122. const el: any = vnode.elm
  123. // call enter callback now
  124. if (el._enterCb) {
  125. el._enterCb.cancelled = true
  126. el._enterCb()
  127. }
  128. const data = resolveTransition(vnode.data.transition)
  129. if (!data) {
  130. return rm()
  131. }
  132. /* istanbul ignore if */
  133. if (el._leaveCb || el.nodeType !== 1) {
  134. return
  135. }
  136. const {
  137. css,
  138. type,
  139. leaveClass,
  140. leaveToClass,
  141. leaveActiveClass,
  142. beforeLeave,
  143. leave,
  144. afterLeave,
  145. leaveCancelled,
  146. delayLeave
  147. } = data
  148. const expectsCSS = css !== false && !isIE9
  149. const userWantsControl =
  150. leave &&
  151. // leave hook may be a bound method which exposes
  152. // the length of original fn as _length
  153. (leave._length || leave.length) > 1
  154. const cb = el._leaveCb = once(() => {
  155. if (el.parentNode && el.parentNode._pending) {
  156. el.parentNode._pending[vnode.key] = null
  157. }
  158. if (expectsCSS) {
  159. removeTransitionClass(el, leaveToClass)
  160. removeTransitionClass(el, leaveActiveClass)
  161. }
  162. if (cb.cancelled) {
  163. if (expectsCSS) {
  164. removeTransitionClass(el, leaveClass)
  165. }
  166. leaveCancelled && leaveCancelled(el)
  167. } else {
  168. rm()
  169. afterLeave && afterLeave(el)
  170. }
  171. el._leaveCb = null
  172. })
  173. if (delayLeave) {
  174. delayLeave(performLeave)
  175. } else {
  176. performLeave()
  177. }
  178. function performLeave () {
  179. // the delayed leave may have already been cancelled
  180. if (cb.cancelled) {
  181. return
  182. }
  183. // record leaving element
  184. if (!vnode.data.show) {
  185. (el.parentNode._pending || (el.parentNode._pending = {}))[vnode.key] = vnode
  186. }
  187. beforeLeave && beforeLeave(el)
  188. if (expectsCSS) {
  189. addTransitionClass(el, leaveClass)
  190. addTransitionClass(el, leaveActiveClass)
  191. nextFrame(() => {
  192. addTransitionClass(el, leaveToClass)
  193. removeTransitionClass(el, leaveClass)
  194. if (!cb.cancelled && !userWantsControl) {
  195. whenTransitionEnds(el, type, cb)
  196. }
  197. })
  198. }
  199. leave && leave(el, cb)
  200. if (!expectsCSS && !userWantsControl) {
  201. cb()
  202. }
  203. }
  204. }
  205. function resolveTransition (def?: string | Object): ?Object {
  206. if (!def) {
  207. return
  208. }
  209. /* istanbul ignore else */
  210. if (typeof def === 'object') {
  211. const res = {}
  212. if (def.css !== false) {
  213. extend(res, autoCssTransition(def.name || 'v'))
  214. }
  215. extend(res, def)
  216. return res
  217. } else if (typeof def === 'string') {
  218. return autoCssTransition(def)
  219. }
  220. }
  221. const autoCssTransition: (name: string) => Object = cached(name => {
  222. return {
  223. enterClass: `${name}-enter`,
  224. leaveClass: `${name}-leave`,
  225. appearClass: `${name}-enter`,
  226. enterToClass: `${name}-enter-to`,
  227. leaveToClass: `${name}-leave-to`,
  228. appearToClass: `${name}-enter-to`,
  229. enterActiveClass: `${name}-enter-active`,
  230. leaveActiveClass: `${name}-leave-active`,
  231. appearActiveClass: `${name}-enter-active`
  232. }
  233. })
  234. function once (fn: Function): Function {
  235. let called = false
  236. return () => {
  237. if (!called) {
  238. called = true
  239. fn()
  240. }
  241. }
  242. }
  243. function _enter (_: any, vnode: VNodeWithData) {
  244. if (!vnode.data.show) {
  245. enter(vnode)
  246. }
  247. }
  248. export default inBrowser ? {
  249. create: _enter,
  250. activate: _enter,
  251. remove (vnode: VNode, rm: Function) {
  252. /* istanbul ignore else */
  253. if (!vnode.data.show) {
  254. leave(vnode, rm)
  255. } else {
  256. rm()
  257. }
  258. }
  259. } : {}