animate-height
Version:
Animate the height of an element to 'auto' or '0px'.
114 lines (111 loc) • 4.12 kB
JavaScript
var AnimationState;
(function (AnimationState) {
AnimationState[AnimationState["None"] = 0] = "None";
AnimationState[AnimationState["Auto"] = 1] = "Auto";
AnimationState[AnimationState["Zero"] = 2] = "Zero";
})(AnimationState || (AnimationState = {}));
const defaultOptions = {
duration: 300,
timing: 'ease',
};
class Animator {
constructor(el) {
this.state = AnimationState.None;
this.orgTransition = '';
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 } = defaultOptions) {
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 } = defaultOptions) {
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 = getComputedStyle(el).height;
height = height ? parseInt(height.replace(/px/, ''), 10) : 0;
if (height > 0) {
requestAnimationFrame(awaitCompletion);
return;
}
res();
});
});
}
handleEvent(ev) {
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;
}
}
/**
* Animate the height of an element to 'auto'.
*
* @param el - The element to animate
* @param options - Animation options
* @returns The height of the element after the animation completes.
*/
const autoHeight = (el, options) => new Animator(el).autoHeight(options);
/**
* Animate the height of an element to '0px'.
*
* @param el - The element to animate
* @param options - Animation options
*/
const zeroHeight = (el, options) => new Animator(el).zeroHeight(options);
export { Animator as HeightAnimator, autoHeight, zeroHeight };