transition-group.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. /* @flow */
  2. // Provides transition support for list items.
  3. // supports move transitions using the FLIP technique.
  4. // Because the vdom's children update algorithm is "unstable" - i.e.
  5. // it doesn't guarantee the relative positioning of removed elements,
  6. // we force transition-group to update its children into two passes:
  7. // in the first pass, we remove all nodes that need to be removed,
  8. // triggering their leaving transition; in the second pass, we insert/move
  9. // into the final disired state. This way in the second pass removed
  10. // nodes will remain where they should be.
  11. import { warn, extend } from 'core/util/index'
  12. import { transitionProps, extractTransitionData } from './transition'
  13. import {
  14. hasTransition,
  15. addTransitionClass,
  16. removeTransitionClass,
  17. getTransitionInfo,
  18. transitionEndEvent
  19. } from '../transition-util'
  20. const props = extend({
  21. tag: String,
  22. moveClass: String
  23. }, transitionProps)
  24. delete props.mode
  25. export default {
  26. props,
  27. render (h: Function) {
  28. const tag = this.tag || this.$vnode.data.tag || 'span'
  29. const map = Object.create(null)
  30. const prevChildren = this.prevChildren = this.children
  31. const rawChildren = this.$slots.default || []
  32. const children = this.children = []
  33. const transitionData = extractTransitionData(this)
  34. for (let i = 0; i < rawChildren.length; i++) {
  35. const c = rawChildren[i]
  36. if (c.tag) {
  37. if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {
  38. children.push(c)
  39. map[c.key] = c
  40. ;(c.data || (c.data = {})).transition = transitionData
  41. } else if (process.env.NODE_ENV !== 'production') {
  42. const opts = c.componentOptions
  43. const name = opts
  44. ? (opts.Ctor.options.name || opts.tag)
  45. : c.tag
  46. warn(`<transition-group> children must be keyed: <${name}>`)
  47. }
  48. }
  49. }
  50. if (prevChildren) {
  51. const kept = []
  52. const removed = []
  53. for (let i = 0; i < prevChildren.length; i++) {
  54. const c = prevChildren[i]
  55. c.data.transition = transitionData
  56. c.data.pos = c.elm.getBoundingClientRect()
  57. if (map[c.key]) {
  58. kept.push(c)
  59. } else {
  60. removed.push(c)
  61. }
  62. }
  63. this.kept = h(tag, null, kept)
  64. this.removed = removed
  65. }
  66. return h(tag, null, children)
  67. },
  68. beforeUpdate () {
  69. // force removing pass
  70. this.__patch__(
  71. this._vnode,
  72. this.kept,
  73. false, // hydrating
  74. true // removeOnly (!important, avoids unnecessary moves)
  75. )
  76. this._vnode = this.kept
  77. },
  78. updated () {
  79. const children = this.prevChildren
  80. const moveClass = this.moveClass || (this.name + '-move')
  81. if (!children.length || !this.hasMove(children[0].elm, moveClass)) {
  82. return
  83. }
  84. children.forEach(c => {
  85. /* istanbul ignore if */
  86. if (c.elm._moveCb) {
  87. c.elm._moveCb()
  88. }
  89. /* istanbul ignore if */
  90. if (c.elm._enterCb) {
  91. c.elm._enterCb()
  92. }
  93. const oldPos = c.data.pos
  94. const newPos = c.data.pos = c.elm.getBoundingClientRect()
  95. const dx = oldPos.left - newPos.left
  96. const dy = oldPos.top - newPos.top
  97. if (dx || dy) {
  98. c.data.moved = true
  99. const s = c.elm.style
  100. s.transform = s.WebkitTransform = `translate(${dx}px,${dy}px)`
  101. s.transitionDuration = '0s'
  102. }
  103. })
  104. // force reflow to put everything in position
  105. const f = document.body.offsetHeight // eslint-disable-line
  106. children.forEach(c => {
  107. if (c.data.moved) {
  108. var el = c.elm
  109. var s = el.style
  110. addTransitionClass(el, moveClass)
  111. s.transform = s.WebkitTransform = s.transitionDuration = ''
  112. el._moveDest = c.data.pos
  113. el.addEventListener(transitionEndEvent, el._moveCb = function cb (e) {
  114. if (!e || /transform$/.test(e.propertyName)) {
  115. el.removeEventListener(transitionEndEvent, cb)
  116. el._moveCb = null
  117. removeTransitionClass(el, moveClass)
  118. }
  119. })
  120. }
  121. })
  122. },
  123. methods: {
  124. hasMove (el: Element, moveClass: string): boolean {
  125. /* istanbul ignore if */
  126. if (!hasTransition) {
  127. return false
  128. }
  129. if (this._hasMove != null) {
  130. return this._hasMove
  131. }
  132. addTransitionClass(el, moveClass)
  133. const info = getTransitionInfo(el)
  134. removeTransitionClass(el, moveClass)
  135. return (this._hasMove = info.hasTransform)
  136. }
  137. }
  138. }