UNPKG

animate-height

Version:

Animate the height of an element to 'auto' or '0px'.

132 lines (107 loc) 3.63 kB
enum AnimationState { None, Auto, Zero, } export interface Options { duration?: number timing?: string } export const defaultOptions = { duration: 300, timing: 'ease', } export default class Animator { el: HTMLElement state: AnimationState = AnimationState.None orgTransition: string = '' constructor(el: HTMLElement) { this.el = el this.el.addEventListener('transitionend', this) } /** * Animate the height of the element to 'auto'. * * @param options - Animation options * @returns The height of the element after the animation completes. */ autoHeight({ duration, timing }: Options = defaultOptions): Promise<number> { return new Promise(res => { const { el } = this const style = getComputedStyle(el) duration = duration || defaultOptions.duration this.orgTransition = style.transition const transition = [ this.orgTransition, `height ${duration}ms ${timing || defaultOptions.timing}`.trim(), ].filter(t => t).join(', ') this.state = AnimationState.Auto el.style.transition = transition el.style.height = style.height const scrollHeight = `${el.scrollHeight}px` requestAnimationFrame(() => el.style.height = scrollHeight) const start = performance.now() requestAnimationFrame(function awaitCompletion() { const height = getComputedStyle(el).height if ((performance.now() - start) < duration! && height !== scrollHeight) { requestAnimationFrame(awaitCompletion) return } res(height ? parseInt(height.replace(/px/, ''), 10) : 0) }) }) } /** * Animate the height of the element to '0px'. * * @param options - Animation options * @param timing - CSS transition timing function */ zeroHeight({ duration, timing }: Options = defaultOptions): Promise<void> { return new Promise(res => { const { el } = this const style = getComputedStyle(el) this.orgTransition = style.transition const transition = [ this.orgTransition, `height ${duration || defaultOptions.duration}ms ${timing || defaultOptions.timing}`.trim(), ].filter(t => t).join(', ') this.state = AnimationState.Zero el.style.transition = transition el.style.height = style.height requestAnimationFrame(() => el.style.height = '0') requestAnimationFrame(function awaitCompletion() { let height: string|number|null = getComputedStyle(el).height height = height ? parseInt(height.replace(/px/, ''), 10) : 0 if (height > 0) { requestAnimationFrame(awaitCompletion) return } res() }) }) } handleEvent(ev: Event) { switch (ev.type) { case 'transitionend': this.onTransitionEnded() break } } onTransitionEnded() { if (this.state === AnimationState.Auto) { this.el.style.height = 'auto' } this.el.style.transition = this.orgTransition this.orgTransition = '' this.state = AnimationState.None } }