2 * Used in the function below to store references of clean-up functions.
3 * Used to ensure only one transitionend function exists at any time.
4 * @type {WeakMap<object, any>}
6 const animateStylesCleanupMap = new WeakMap();
9 * Fade in the given element.
10 * @param {Element} element
11 * @param {Number} animTime
12 * @param {Function|null} onComplete
14 export function fadeIn(element, animTime = 400, onComplete = null) {
15 cleanupExistingElementAnimation(element);
16 element.style.display = 'block';
17 animateStyles(element, {
20 if (onComplete) onComplete();
25 * Fade out the given element.
26 * @param {Element} element
27 * @param {Number} animTime
28 * @param {Function|null} onComplete
30 export function fadeOut(element, animTime = 400, onComplete = null) {
31 cleanupExistingElementAnimation(element);
32 animateStyles(element, {
35 element.style.display = 'none';
36 if (onComplete) onComplete();
41 * Hide the element by sliding the contents upwards.
42 * @param {Element} element
43 * @param {Number} animTime
45 export function slideUp(element, animTime = 400) {
46 cleanupExistingElementAnimation(element);
47 const currentHeight = element.getBoundingClientRect().height;
48 const computedStyles = getComputedStyle(element);
49 const currentPaddingTop = computedStyles.getPropertyValue('padding-top');
50 const currentPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
52 height: [`${currentHeight}px`, '0px'],
53 overflow: ['hidden', 'hidden'],
54 paddingTop: [currentPaddingTop, '0px'],
55 paddingBottom: [currentPaddingBottom, '0px'],
58 animateStyles(element, animStyles, animTime, () => {
59 element.style.display = 'none';
64 * Show the given element by expanding the contents.
65 * @param {Element} element - Element to animate
66 * @param {Number} animTime - Animation time in ms
68 export function slideDown(element, animTime = 400) {
69 cleanupExistingElementAnimation(element);
70 element.style.display = 'block';
71 const targetHeight = element.getBoundingClientRect().height;
72 const computedStyles = getComputedStyle(element);
73 const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
74 const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
76 height: ['0px', `${targetHeight}px`],
77 overflow: ['hidden', 'hidden'],
78 paddingTop: ['0px', targetPaddingTop],
79 paddingBottom: ['0px', targetPaddingBottom],
82 animateStyles(element, animStyles, animTime);
86 * Animate the css styles of an element using FLIP animation techniques.
87 * Styles must be an object where the keys are style properties, camelcase, and the values
88 * are an array of two items in the format [initialValue, finalValue]
89 * @param {Element} element
90 * @param {Object} styles
91 * @param {Number} animTime
92 * @param {Function} onComplete
94 function animateStyles(element, styles, animTime = 400, onComplete = null) {
95 const styleNames = Object.keys(styles);
96 for (let style of styleNames) {
97 element.style[style] = styles[style][0];
100 const cleanup = () => {
101 for (let style of styleNames) {
102 element.style[style] = null;
104 element.style.transition = null;
105 element.removeEventListener('transitionend', cleanup);
106 animateStylesCleanupMap.delete(element);
107 if (onComplete) onComplete();
111 element.style.transition = `all ease-in-out ${animTime}ms`;
112 for (let style of styleNames) {
113 element.style[style] = styles[style][1];
116 element.addEventListener('transitionend', cleanup);
117 animateStylesCleanupMap.set(element, cleanup);
122 * Run the active cleanup action for the given element.
123 * @param {Element} element
125 function cleanupExistingElementAnimation(element) {
126 if (animateStylesCleanupMap.has(element)) {
127 const oldCleanup = animateStylesCleanupMap.get(element);