transition.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  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.context === vnode.context &&
  94. pendingNode.tag === vnode.tag &&
  95. pendingNode.elm._leaveCb) {
  96. pendingNode.elm._leaveCb()
  97. }
  98. enterHook && enterHook(el, cb)
  99. }, 'transition-insert')
  100. }
  101. // start enter transition
  102. beforeEnterHook && beforeEnterHook(el)
  103. if (expectsCSS) {
  104. addTransitionClass(el, startClass)
  105. addTransitionClass(el, activeClass)
  106. nextFrame(() => {
  107. addTransitionClass(el, toClass)
  108. removeTransitionClass(el, startClass)
  109. if (!cb.cancelled && !userWantsControl) {
  110. whenTransitionEnds(el, type, cb)
  111. }
  112. })
  113. }
  114. if (vnode.data.show) {
  115. toggleDisplay && toggleDisplay()
  116. enterHook && enterHook(el, cb)
  117. }
  118. if (!expectsCSS && !userWantsControl) {
  119. cb()
  120. }
  121. }
  122. export function leave (vnode: VNodeWithData, rm: Function) {
  123. const el: any = vnode.elm
  124. // call enter callback now
  125. if (el._enterCb) {
  126. el._enterCb.cancelled = true
  127. el._enterCb()
  128. }
  129. const data = resolveTransition(vnode.data.transition)
  130. if (!data) {
  131. return rm()
  132. }
  133. /* istanbul ignore if */
  134. if (el._leaveCb || el.nodeType !== 1) {
  135. return
  136. }
  137. const {
  138. css,
  139. type,
  140. leaveClass,
  141. leaveToClass,
  142. leaveActiveClass,
  143. beforeLeave,
  144. leave,
  145. afterLeave,
  146. leaveCancelled,
  147. delayLeave
  148. } = data
  149. const expectsCSS = css !== false && !isIE9
  150. const userWantsControl =
  151. leave &&
  152. // leave hook may be a bound method which exposes
  153. // the length of original fn as _length
  154. (leave._length || leave.length) > 1
  155. const cb = el._leaveCb = once(() => {
  156. if (el.parentNode && el.parentNode._pending) {
  157. el.parentNode._pending[vnode.key] = null
  158. }
  159. if (expectsCSS) {
  160. removeTransitionClass(el, leaveToClass)
  161. removeTransitionClass(el, leaveActiveClass)
  162. }
  163. if (cb.cancelled) {
  164. if (expectsCSS) {
  165. removeTransitionClass(el, leaveClass)
  166. }
  167. leaveCancelled && leaveCancelled(el)
  168. } else {
  169. rm()
  170. afterLeave && afterLeave(el)
  171. }
  172. el._leaveCb = null
  173. })
  174. if (delayLeave) {
  175. delayLeave(performLeave)
  176. } else {
  177. performLeave()
  178. }
  179. function performLeave () {
  180. // the delayed leave may have already been cancelled
  181. if (cb.cancelled) {
  182. return
  183. }
  184. // record leaving element
  185. if (!vnode.data.show) {
  186. (el.parentNode._pending || (el.parentNode._pending = {}))[vnode.key] = vnode
  187. }
  188. beforeLeave && beforeLeave(el)
  189. if (expectsCSS) {
  190. addTransitionClass(el, leaveClass)
  191. addTransitionClass(el, leaveActiveClass)
  192. nextFrame(() => {
  193. addTransitionClass(el, leaveToClass)
  194. removeTransitionClass(el, leaveClass)
  195. if (!cb.cancelled && !userWantsControl) {
  196. whenTransitionEnds(el, type, cb)
  197. }
  198. })
  199. }
  200. leave && leave(el, cb)
  201. if (!expectsCSS && !userWantsControl) {
  202. cb()
  203. }
  204. }
  205. }
  206. function resolveTransition (def?: string | Object): ?Object {
  207. if (!def) {
  208. return
  209. }
  210. /* istanbul ignore else */
  211. if (typeof def === 'object') {
  212. const res = {}
  213. if (def.css !== false) {
  214. extend(res, autoCssTransition(def.name || 'v'))
  215. }
  216. extend(res, def)
  217. return res
  218. } else if (typeof def === 'string') {
  219. return autoCssTransition(def)
  220. }
  221. }
  222. const autoCssTransition: (name: string) => Object = cached(name => {
  223. return {
  224. enterClass: `${name}-enter`,
  225. leaveClass: `${name}-leave`,
  226. appearClass: `${name}-enter`,
  227. enterToClass: `${name}-enter-to`,
  228. leaveToClass: `${name}-leave-to`,
  229. appearToClass: `${name}-enter-to`,
  230. enterActiveClass: `${name}-enter-active`,
  231. leaveActiveClass: `${name}-leave-active`,
  232. appearActiveClass: `${name}-enter-active`
  233. }
  234. })
  235. function once (fn: Function): Function {
  236. let called = false
  237. return () => {
  238. if (!called) {
  239. called = true
  240. fn()
  241. }
  242. }
  243. }
  244. function _enter (_: any, vnode: VNodeWithData) {
  245. if (!vnode.data.show) {
  246. enter(vnode)
  247. }
  248. }
  249. export default inBrowser ? {
  250. create: _enter,
  251. activate: _enter,
  252. remove (vnode: VNode, rm: Function) {
  253. /* istanbul ignore else */
  254. if (!vnode.data.show) {
  255. leave(vnode, rm)
  256. } else {
  257. rm()
  258. }
  259. }
  260. } : {}