animate-height
Version:
Animate the height of an element to 'auto' or '0px'.
132 lines (107 loc) • 3.63 kB
text/typescript
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
}
}