vue
Version:
Reactive, component-oriented view layer for modern web interfaces.
191 lines (174 loc) • 5.48 kB
JavaScript
/* @flow */
import { inBrowser, isIE9 } from 'core/util/index'
import { addClass, removeClass } from './class-util'
import { remove, extend, cached } from 'shared/util'
export function resolveTransition (def?: string | Object): ?Object {
if (!def) {
return
}
/* istanbul ignore else */
if (typeof def === 'object') {
const res = {}
if (def.css !== false) {
extend(res, autoCssTransition(def.name || 'v'))
}
extend(res, def)
return res
} else if (typeof def === 'string') {
return autoCssTransition(def)
}
}
const autoCssTransition: (name: string) => Object = cached(name => {
return {
enterClass: `${name}-enter`,
enterToClass: `${name}-enter-to`,
enterActiveClass: `${name}-enter-active`,
leaveClass: `${name}-leave`,
leaveToClass: `${name}-leave-to`,
leaveActiveClass: `${name}-leave-active`
}
})
export const hasTransition = inBrowser && !isIE9
const TRANSITION = 'transition'
const ANIMATION = 'animation'
// Transition property/event sniffing
export let transitionProp = 'transition'
export let transitionEndEvent = 'transitionend'
export let animationProp = 'animation'
export let animationEndEvent = 'animationend'
if (hasTransition) {
/* istanbul ignore if */
if (window.ontransitionend === undefined &&
window.onwebkittransitionend !== undefined
) {
transitionProp = 'WebkitTransition'
transitionEndEvent = 'webkitTransitionEnd'
}
if (window.onanimationend === undefined &&
window.onwebkitanimationend !== undefined
) {
animationProp = 'WebkitAnimation'
animationEndEvent = 'webkitAnimationEnd'
}
}
// binding to window is necessary to make hot reload work in IE in strict mode
const raf = inBrowser
? window.requestAnimationFrame
? window.requestAnimationFrame.bind(window)
: setTimeout
: /* istanbul ignore next */ fn => fn()
export function nextFrame (fn: Function) {
raf(() => {
raf(fn)
})
}
export function addTransitionClass (el: any, cls: string) {
const transitionClasses = el._transitionClasses || (el._transitionClasses = [])
if (transitionClasses.indexOf(cls) < 0) {
transitionClasses.push(cls)
addClass(el, cls)
}
}
export function removeTransitionClass (el: any, cls: string) {
if (el._transitionClasses) {
remove(el._transitionClasses, cls)
}
removeClass(el, cls)
}
export function whenTransitionEnds (
el: Element,
expectedType: ?string,
cb: Function
) {
const { type, timeout, propCount } = getTransitionInfo(el, expectedType)
if (!type) return cb()
const event: string = type === TRANSITION ? transitionEndEvent : animationEndEvent
let ended = 0
const end = () => {
el.removeEventListener(event, onEnd)
cb()
}
const onEnd = e => {
if (e.target === el) {
if (++ended >= propCount) {
end()
}
}
}
setTimeout(() => {
if (ended < propCount) {
end()
}
}, timeout + 1)
el.addEventListener(event, onEnd)
}
const transformRE = /\b(transform|all)(,|$)/
export function getTransitionInfo (el: Element, expectedType?: ?string): {
type: ?string;
propCount: number;
timeout: number;
hasTransform: boolean;
} {
const styles: any = window.getComputedStyle(el)
// JSDOM may return undefined for transition properties
const transitionDelays: Array<string> = (styles[transitionProp + 'Delay'] || '').split(', ')
const transitionDurations: Array<string> = (styles[transitionProp + 'Duration'] || '').split(', ')
const transitionTimeout: number = getTimeout(transitionDelays, transitionDurations)
const animationDelays: Array<string> = (styles[animationProp + 'Delay'] || '').split(', ')
const animationDurations: Array<string> = (styles[animationProp + 'Duration'] || '').split(', ')
const animationTimeout: number = getTimeout(animationDelays, animationDurations)
let type: ?string
let timeout = 0
let propCount = 0
/* istanbul ignore if */
if (expectedType === TRANSITION) {
if (transitionTimeout > 0) {
type = TRANSITION
timeout = transitionTimeout
propCount = transitionDurations.length
}
} else if (expectedType === ANIMATION) {
if (animationTimeout > 0) {
type = ANIMATION
timeout = animationTimeout
propCount = animationDurations.length
}
} else {
timeout = Math.max(transitionTimeout, animationTimeout)
type = timeout > 0
? transitionTimeout > animationTimeout
? TRANSITION
: ANIMATION
: null
propCount = type
? type === TRANSITION
? transitionDurations.length
: animationDurations.length
: 0
}
const hasTransform: boolean =
type === TRANSITION &&
transformRE.test(styles[transitionProp + 'Property'])
return {
type,
timeout,
propCount,
hasTransform
}
}
function getTimeout (delays: Array<string>, durations: Array<string>): number {
/* istanbul ignore next */
while (delays.length < durations.length) {
delays = delays.concat(delays)
}
return Math.max.apply(null, durations.map((d, i) => {
return toMs(d) + toMs(delays[i])
}))
}
// Old versions of Chromium (below 61.0.3163.100) formats floating pointer numbers
// in a locale-dependent way, using a comma instead of a dot.
// If comma is not replaced with a dot, the input will be rounded down (i.e. acting
// as a floor function) causing unexpected behaviors
function toMs (s: string): number {
return Number(s.slice(0, -1).replace(',', '.')) * 1000
}