vue
Version:
Reactive, component-oriented view layer for modern web interfaces.
216 lines (200 loc) • 5.67 kB
text/typescript
import { inBrowser, isIE9 } from 'core/util/index'
import { addClass, removeClass } from 'web/runtime/class-util'
import { remove, extend, cached } from 'shared/util'
export function resolveTransition(
def?: string | Record<string, any>
): Record<string, any> | undefined {
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(() => {
// @ts-expect-error
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 | undefined,
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 | null
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 | undefined | null
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
}