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 * Animate the css styles of an element using FLIP animation techniques.
10 * Styles must be an object where the keys are style properties, camelcase, and the values
11 * are an array of two items in the format [initialValue, finalValue]
12 * @param {Element} element
13 * @param {Object} styles
14 * @param {Number} animTime
15 * @param {Function} onComplete
17 function animateStyles(element, styles, animTime = 400, onComplete = null) {
18 const styleNames = Object.keys(styles);
19 for (const style of styleNames) {
20 element.style[style] = styles[style][0];
23 const cleanup = () => {
24 for (const style of styleNames) {
25 element.style[style] = null;
27 element.style.transition = null;
28 element.removeEventListener('transitionend', cleanup);
29 animateStylesCleanupMap.delete(element);
30 if (onComplete) onComplete();
34 element.style.transition = `all ease-in-out ${animTime}ms`;
35 for (const style of styleNames) {
36 element.style[style] = styles[style][1];
39 element.addEventListener('transitionend', cleanup);
40 animateStylesCleanupMap.set(element, cleanup);
45 * Run the active cleanup action for the given element.
46 * @param {Element} element
48 function cleanupExistingElementAnimation(element) {
49 if (animateStylesCleanupMap.has(element)) {
50 const oldCleanup = animateStylesCleanupMap.get(element);
56 * Fade in the given element.
57 * @param {Element} element
58 * @param {Number} animTime
59 * @param {Function|null} onComplete
61 export function fadeIn(element, animTime = 400, onComplete = null) {
62 cleanupExistingElementAnimation(element);
63 element.style.display = 'block';
64 animateStyles(element, {
67 if (onComplete) onComplete();
72 * Fade out the given element.
73 * @param {Element} element
74 * @param {Number} animTime
75 * @param {Function|null} onComplete
77 export function fadeOut(element, animTime = 400, onComplete = null) {
78 cleanupExistingElementAnimation(element);
79 animateStyles(element, {
82 element.style.display = 'none';
83 if (onComplete) onComplete();
88 * Hide the element by sliding the contents upwards.
89 * @param {Element} element
90 * @param {Number} animTime
92 export function slideUp(element, animTime = 400) {
93 cleanupExistingElementAnimation(element);
94 const currentHeight = element.getBoundingClientRect().height;
95 const computedStyles = getComputedStyle(element);
96 const currentPaddingTop = computedStyles.getPropertyValue('padding-top');
97 const currentPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
99 maxHeight: [`${currentHeight}px`, '0px'],
100 overflow: ['hidden', 'hidden'],
101 paddingTop: [currentPaddingTop, '0px'],
102 paddingBottom: [currentPaddingBottom, '0px'],
105 animateStyles(element, animStyles, animTime, () => {
106 element.style.display = 'none';
111 * Show the given element by expanding the contents.
112 * @param {Element} element - Element to animate
113 * @param {Number} animTime - Animation time in ms
115 export function slideDown(element, animTime = 400) {
116 cleanupExistingElementAnimation(element);
117 element.style.display = 'block';
118 const targetHeight = element.getBoundingClientRect().height;
119 const computedStyles = getComputedStyle(element);
120 const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
121 const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
123 maxHeight: ['0px', `${targetHeight}px`],
124 overflow: ['hidden', 'hidden'],
125 paddingTop: ['0px', targetPaddingTop],
126 paddingBottom: ['0px', targetPaddingBottom],
129 animateStyles(element, animStyles, animTime);
133 * Transition the height of the given element between two states.
134 * Call with first state, and you'll receive a function in return.
135 * Call the returned function in the second state to animate between those two states.
136 * If animating to/from 0-height use the slide-up/slide down as easier alternatives.
137 * @param {Element} element - Element to animate
138 * @param {Number} animTime - Animation time in ms
139 * @returns {function} - Function to run in second state to trigger animation.
141 export function transitionHeight(element, animTime = 400) {
142 const startHeight = element.getBoundingClientRect().height;
143 const initialComputedStyles = getComputedStyle(element);
144 const startPaddingTop = initialComputedStyles.getPropertyValue('padding-top');
145 const startPaddingBottom = initialComputedStyles.getPropertyValue('padding-bottom');
148 cleanupExistingElementAnimation(element);
149 const targetHeight = element.getBoundingClientRect().height;
150 const computedStyles = getComputedStyle(element);
151 const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
152 const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
154 height: [`${startHeight}px`, `${targetHeight}px`],
155 overflow: ['hidden', 'hidden'],
156 paddingTop: [startPaddingTop, targetPaddingTop],
157 paddingBottom: [startPaddingBottom, targetPaddingBottom],
160 animateStyles(element, animStyles, animTime);