64c1533c6de84acf79d739e28a11801570428e8b7e8d621d72262c67817b809afd144a1fb405fb85e5e555bfec87b5ae49a48fda42ac980f303249d260dc3c 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. import { warn } from 'core/util/debug'
  2. import { extend, once, noop } from 'shared/util'
  3. import { activeInstance } from 'core/instance/lifecycle'
  4. import { resolveTransition } from 'web/runtime/transition-util'
  5. export default {
  6. create: enter,
  7. activate: enter,
  8. remove: leave
  9. }
  10. function enter (_, vnode) {
  11. const el = vnode.elm
  12. // call leave callback now
  13. if (el._leaveCb) {
  14. el._leaveCb.cancelled = true
  15. el._leaveCb()
  16. }
  17. const data = resolveTransition(vnode.data.transition)
  18. if (!data) {
  19. return
  20. }
  21. /* istanbul ignore if */
  22. if (el._enterCb) {
  23. return
  24. }
  25. const {
  26. enterClass,
  27. enterToClass,
  28. enterActiveClass,
  29. appearClass,
  30. appearToClass,
  31. appearActiveClass,
  32. beforeEnter,
  33. enter,
  34. afterEnter,
  35. enterCancelled,
  36. beforeAppear,
  37. appear,
  38. afterAppear,
  39. appearCancelled
  40. } = data
  41. let context = activeInstance
  42. let transitionNode = activeInstance.$vnode
  43. while (transitionNode && transitionNode.parent) {
  44. transitionNode = transitionNode.parent
  45. context = transitionNode.context
  46. }
  47. const isAppear = !context._isMounted || !vnode.isRootInsert
  48. if (isAppear && !appear && appear !== '') {
  49. return
  50. }
  51. const startClass = isAppear ? appearClass : enterClass
  52. const toClass = isAppear ? appearToClass : enterToClass
  53. const activeClass = isAppear ? appearActiveClass : enterActiveClass
  54. const beforeEnterHook = isAppear ? (beforeAppear || beforeEnter) : beforeEnter
  55. const enterHook = isAppear ? (typeof appear === 'function' ? appear : enter) : enter
  56. const afterEnterHook = isAppear ? (afterAppear || afterEnter) : afterEnter
  57. const enterCancelledHook = isAppear ? (appearCancelled || enterCancelled) : enterCancelled
  58. const userWantsControl =
  59. enterHook &&
  60. // enterHook may be a bound method which exposes
  61. // the length of original fn as _length
  62. (enterHook._length || enterHook.length) > 1
  63. const stylesheet = vnode.context.$options.style || {}
  64. const startState = stylesheet[startClass]
  65. const transitionProperties = (stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][activeClass]) || {}
  66. const endState = getEnterTargetState(el, stylesheet, startClass, toClass, activeClass)
  67. const needAnimation = Object.keys(endState).length > 0
  68. const cb = el._enterCb = once(() => {
  69. if (cb.cancelled) {
  70. enterCancelledHook && enterCancelledHook(el)
  71. } else {
  72. afterEnterHook && afterEnterHook(el)
  73. }
  74. el._enterCb = null
  75. })
  76. // We need to wait until the native element has been inserted, but currently
  77. // there's no API to do that. So we have to wait "one frame" - not entirely
  78. // sure if this is guaranteed to be enough (e.g. on slow devices?)
  79. setTimeout(() => {
  80. const parent = el.parentNode
  81. const pendingNode = parent && parent._pending && parent._pending[vnode.key]
  82. if (pendingNode &&
  83. pendingNode.context === vnode.context &&
  84. pendingNode.tag === vnode.tag &&
  85. pendingNode.elm._leaveCb
  86. ) {
  87. pendingNode.elm._leaveCb()
  88. }
  89. enterHook && enterHook(el, cb)
  90. if (needAnimation) {
  91. const animation = vnode.context.$requireWeexModule('animation')
  92. animation.transition(el.ref, {
  93. styles: endState,
  94. duration: transitionProperties.duration || 0,
  95. delay: transitionProperties.delay || 0,
  96. timingFunction: transitionProperties.timingFunction || 'linear'
  97. }, userWantsControl ? noop : cb)
  98. } else if (!userWantsControl) {
  99. cb()
  100. }
  101. }, 16)
  102. // start enter transition
  103. beforeEnterHook && beforeEnterHook(el)
  104. if (startState) {
  105. if (typeof el.setStyles === 'function') {
  106. el.setStyles(startState)
  107. } else {
  108. for (const key in startState) {
  109. el.setStyle(key, startState[key])
  110. }
  111. }
  112. }
  113. if (!needAnimation && !userWantsControl) {
  114. cb()
  115. }
  116. }
  117. function leave (vnode, rm) {
  118. const el = vnode.elm
  119. // call enter callback now
  120. if (el._enterCb) {
  121. el._enterCb.cancelled = true
  122. el._enterCb()
  123. }
  124. const data = resolveTransition(vnode.data.transition)
  125. if (!data) {
  126. return rm()
  127. }
  128. if (el._leaveCb) {
  129. return
  130. }
  131. const {
  132. leaveClass,
  133. leaveToClass,
  134. leaveActiveClass,
  135. beforeLeave,
  136. leave,
  137. afterLeave,
  138. leaveCancelled,
  139. delayLeave
  140. } = data
  141. const userWantsControl =
  142. leave &&
  143. // leave hook may be a bound method which exposes
  144. // the length of original fn as _length
  145. (leave._length || leave.length) > 1
  146. const stylesheet = vnode.context.$options.style || {}
  147. const startState = stylesheet[leaveClass]
  148. const endState = stylesheet[leaveToClass] || stylesheet[leaveActiveClass]
  149. const transitionProperties = (stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][leaveActiveClass]) || {}
  150. const cb = el._leaveCb = once(() => {
  151. if (el.parentNode && el.parentNode._pending) {
  152. el.parentNode._pending[vnode.key] = null
  153. }
  154. if (cb.cancelled) {
  155. leaveCancelled && leaveCancelled(el)
  156. } else {
  157. rm()
  158. afterLeave && afterLeave(el)
  159. }
  160. el._leaveCb = null
  161. })
  162. if (delayLeave) {
  163. delayLeave(performLeave)
  164. } else {
  165. performLeave()
  166. }
  167. function performLeave () {
  168. const animation = vnode.context.$requireWeexModule('animation')
  169. // the delayed leave may have already been cancelled
  170. if (cb.cancelled) {
  171. return
  172. }
  173. // record leaving element
  174. if (!vnode.data.show) {
  175. (el.parentNode._pending || (el.parentNode._pending = {}))[vnode.key] = vnode
  176. }
  177. beforeLeave && beforeLeave(el)
  178. if (startState) {
  179. animation.transition(el.ref, {
  180. styles: startState
  181. }, next)
  182. } else {
  183. next()
  184. }
  185. function next () {
  186. animation.transition(el.ref, {
  187. styles: endState,
  188. duration: transitionProperties.duration || 0,
  189. delay: transitionProperties.delay || 0,
  190. timingFunction: transitionProperties.timingFunction || 'linear'
  191. }, userWantsControl ? noop : cb)
  192. }
  193. leave && leave(el, cb)
  194. if (!endState && !userWantsControl) {
  195. cb()
  196. }
  197. }
  198. }
  199. // determine the target animation style for an entering transition.
  200. function getEnterTargetState (el, stylesheet, startClass, endClass, activeClass) {
  201. const targetState = {}
  202. const startState = stylesheet[startClass]
  203. const endState = stylesheet[endClass]
  204. const activeState = stylesheet[activeClass]
  205. // 1. fallback to element's default styling
  206. if (startState) {
  207. for (const key in startState) {
  208. targetState[key] = el.style[key]
  209. if (
  210. process.env.NODE_ENV !== 'production' &&
  211. targetState[key] == null &&
  212. (!activeState || activeState[key] == null) &&
  213. (!endState || endState[key] == null)
  214. ) {
  215. warn(
  216. `transition property "${key}" is declared in enter starting class (.${startClass}), ` +
  217. `but not declared anywhere in enter ending class (.${endClass}), ` +
  218. `enter active cass (.${activeClass}) or the element's default styling. ` +
  219. `Note in Weex, CSS properties need explicit values to be transitionable.`
  220. )
  221. }
  222. }
  223. }
  224. // 2. if state is mixed in active state, extract them while excluding
  225. // transition properties
  226. if (activeState) {
  227. for (const key in activeState) {
  228. if (key.indexOf('transition') !== 0) {
  229. targetState[key] = activeState[key]
  230. }
  231. }
  232. }
  233. // 3. explicit endState has highest priority
  234. if (endState) {
  235. extend(targetState, endState)
  236. }
  237. return targetState
  238. }