6d5ab678a3ede075b9002e1bcc5b27f7bc53ad729077f551e89f62e0c3c48ca18f409f165068e644cbe127b1f03488926b160bd3da1a338b9ea4a8eb05ba52 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. /* @flow */
  2. import { inBrowser, isIE9 } from 'core/util/index'
  3. import { addClass, removeClass } from './class-util'
  4. import { remove, extend, cached } from 'shared/util'
  5. export function resolveTransition (def?: string | Object): ?Object {
  6. if (!def) {
  7. return
  8. }
  9. /* istanbul ignore else */
  10. if (typeof def === 'object') {
  11. const res = {}
  12. if (def.css !== false) {
  13. extend(res, autoCssTransition(def.name || 'v'))
  14. }
  15. extend(res, def)
  16. return res
  17. } else if (typeof def === 'string') {
  18. return autoCssTransition(def)
  19. }
  20. }
  21. const autoCssTransition: (name: string) => Object = cached(name => {
  22. return {
  23. enterClass: `${name}-enter`,
  24. enterToClass: `${name}-enter-to`,
  25. enterActiveClass: `${name}-enter-active`,
  26. leaveClass: `${name}-leave`,
  27. leaveToClass: `${name}-leave-to`,
  28. leaveActiveClass: `${name}-leave-active`
  29. }
  30. })
  31. export const hasTransition = inBrowser && !isIE9
  32. const TRANSITION = 'transition'
  33. const ANIMATION = 'animation'
  34. // Transition property/event sniffing
  35. export let transitionProp = 'transition'
  36. export let transitionEndEvent = 'transitionend'
  37. export let animationProp = 'animation'
  38. export let animationEndEvent = 'animationend'
  39. if (hasTransition) {
  40. /* istanbul ignore if */
  41. if (window.ontransitionend === undefined &&
  42. window.onwebkittransitionend !== undefined
  43. ) {
  44. transitionProp = 'WebkitTransition'
  45. transitionEndEvent = 'webkitTransitionEnd'
  46. }
  47. if (window.onanimationend === undefined &&
  48. window.onwebkitanimationend !== undefined
  49. ) {
  50. animationProp = 'WebkitAnimation'
  51. animationEndEvent = 'webkitAnimationEnd'
  52. }
  53. }
  54. // binding to window is necessary to make hot reload work in IE in strict mode
  55. const raf = inBrowser
  56. ? window.requestAnimationFrame
  57. ? window.requestAnimationFrame.bind(window)
  58. : setTimeout
  59. : /* istanbul ignore next */ fn => fn()
  60. export function nextFrame (fn: Function) {
  61. raf(() => {
  62. raf(fn)
  63. })
  64. }
  65. export function addTransitionClass (el: any, cls: string) {
  66. const transitionClasses = el._transitionClasses || (el._transitionClasses = [])
  67. if (transitionClasses.indexOf(cls) < 0) {
  68. transitionClasses.push(cls)
  69. addClass(el, cls)
  70. }
  71. }
  72. export function removeTransitionClass (el: any, cls: string) {
  73. if (el._transitionClasses) {
  74. remove(el._transitionClasses, cls)
  75. }
  76. removeClass(el, cls)
  77. }
  78. export function whenTransitionEnds (
  79. el: Element,
  80. expectedType: ?string,
  81. cb: Function
  82. ) {
  83. const { type, timeout, propCount } = getTransitionInfo(el, expectedType)
  84. if (!type) return cb()
  85. const event: string = type === TRANSITION ? transitionEndEvent : animationEndEvent
  86. let ended = 0
  87. const end = () => {
  88. el.removeEventListener(event, onEnd)
  89. cb()
  90. }
  91. const onEnd = e => {
  92. if (e.target === el) {
  93. if (++ended >= propCount) {
  94. end()
  95. }
  96. }
  97. }
  98. setTimeout(() => {
  99. if (ended < propCount) {
  100. end()
  101. }
  102. }, timeout + 1)
  103. el.addEventListener(event, onEnd)
  104. }
  105. const transformRE = /\b(transform|all)(,|$)/
  106. export function getTransitionInfo (el: Element, expectedType?: ?string): {
  107. type: ?string;
  108. propCount: number;
  109. timeout: number;
  110. hasTransform: boolean;
  111. } {
  112. const styles: any = window.getComputedStyle(el)
  113. // JSDOM may return undefined for transition properties
  114. const transitionDelays: Array<string> = (styles[transitionProp + 'Delay'] || '').split(', ')
  115. const transitionDurations: Array<string> = (styles[transitionProp + 'Duration'] || '').split(', ')
  116. const transitionTimeout: number = getTimeout(transitionDelays, transitionDurations)
  117. const animationDelays: Array<string> = (styles[animationProp + 'Delay'] || '').split(', ')
  118. const animationDurations: Array<string> = (styles[animationProp + 'Duration'] || '').split(', ')
  119. const animationTimeout: number = getTimeout(animationDelays, animationDurations)
  120. let type: ?string
  121. let timeout = 0
  122. let propCount = 0
  123. /* istanbul ignore if */
  124. if (expectedType === TRANSITION) {
  125. if (transitionTimeout > 0) {
  126. type = TRANSITION
  127. timeout = transitionTimeout
  128. propCount = transitionDurations.length
  129. }
  130. } else if (expectedType === ANIMATION) {
  131. if (animationTimeout > 0) {
  132. type = ANIMATION
  133. timeout = animationTimeout
  134. propCount = animationDurations.length
  135. }
  136. } else {
  137. timeout = Math.max(transitionTimeout, animationTimeout)
  138. type = timeout > 0
  139. ? transitionTimeout > animationTimeout
  140. ? TRANSITION
  141. : ANIMATION
  142. : null
  143. propCount = type
  144. ? type === TRANSITION
  145. ? transitionDurations.length
  146. : animationDurations.length
  147. : 0
  148. }
  149. const hasTransform: boolean =
  150. type === TRANSITION &&
  151. transformRE.test(styles[transitionProp + 'Property'])
  152. return {
  153. type,
  154. timeout,
  155. propCount,
  156. hasTransform
  157. }
  158. }
  159. function getTimeout (delays: Array<string>, durations: Array<string>): number {
  160. /* istanbul ignore next */
  161. while (delays.length < durations.length) {
  162. delays = delays.concat(delays)
  163. }
  164. return Math.max.apply(null, durations.map((d, i) => {
  165. return toMs(d) + toMs(delays[i])
  166. }))
  167. }
  168. // Old versions of Chromium (below 61.0.3163.100) formats floating pointer numbers
  169. // in a locale-dependent way, using a comma instead of a dot.
  170. // If comma is not replaced with a dot, the input will be rounded down (i.e. acting
  171. // as a floor function) causing unexpected behaviors
  172. function toMs (s: string): number {
  173. return Number(s.slice(0, -1).replace(',', '.')) * 1000
  174. }