transition-group.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  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) {
  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.pos = c.elm.getBoundingClientRect()
  56. if (map[c.key]) {
  57. kept.push(c)
  58. } else {
  59. removed.push(c)
  60. }
  61. }
  62. this.kept = h(tag, null, kept)
  63. this.removed = removed
  64. }
  65. return h(tag, null, children)
  66. },
  67. beforeUpdate () {
  68. // force removing pass
  69. this.__patch__(
  70. this._vnode,
  71. this.kept,
  72. false, // hydrating
  73. true // removeOnly (!important, avoids unnecessary moves)
  74. )
  75. this._vnode = this.kept
  76. },
  77. updated () {
  78. const children = this.prevChildren
  79. const moveClass = this.moveClass || (this.name + '-move')
  80. if (!children.length || !this.hasMove(children[0].elm, moveClass)) {
  81. return
  82. }
  83. children.forEach(c => {
  84. const oldPos = c.data.pos
  85. const newPos = c.elm.getBoundingClientRect()
  86. const dx = oldPos.left - newPos.left
  87. const dy = oldPos.top - newPos.top
  88. if (dx || dy) {
  89. c.data.moved = true
  90. const s = c.elm.style
  91. s.transform = s.WebkitTransform = `translate(${dx}px,${dy}px)`
  92. s.transitionDuration = '0s'
  93. }
  94. })
  95. // force reflow to put everything in position
  96. const f = document.body.offsetHeight // eslint-disable-line
  97. children.forEach(c => {
  98. if (c.data.moved) {
  99. const el = c.elm
  100. /* istanbul ignore if */
  101. if (el._pendingMoveCb) {
  102. el._pendingMoveCb()
  103. }
  104. const s = el.style
  105. addTransitionClass(el, moveClass)
  106. s.transform = s.WebkitTransform = s.transitionDuration = ''
  107. el.addEventListener(transitionEndEvent, el._pendingMoveCb = function cb () {
  108. el.removeEventListener(transitionEndEvent, cb)
  109. el._pendingMoveCb = null
  110. removeTransitionClass(el, moveClass)
  111. })
  112. }
  113. })
  114. },
  115. methods: {
  116. hasMove (el: Element, moveClass: string): boolean {
  117. /* istanbul ignore if */
  118. if (!hasTransition) {
  119. return false
  120. }
  121. if (this._hasMove != null) {
  122. return this._hasMove
  123. }
  124. addTransitionClass(el, moveClass)
  125. const info = getTransitionInfo(el)
  126. removeTransitionClass(el, moveClass)
  127. return (this._hasMove = info.hasTransform)
  128. }
  129. }
  130. }