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 * Transition the height of the given element between two states.
87 * Call with first state, and you'll receive a function in return.
88 * Call the returned function in the second state to animate between those two states.
89 * If animating to/from 0-height use the slide-up/slide down as easier alternatives.
90 * @param {Element} element - Element to animate
91 * @param {Number} animTime - Animation time in ms
92 * @returns {function} - Function to run in second state to trigger animation.
94 export function transitionHeight(element, animTime = 400) {
95 const startHeight = element.getBoundingClientRect().height;
96 const initialComputedStyles = getComputedStyle(element);
97 const startPaddingTop = initialComputedStyles.getPropertyValue('padding-top');
98 const startPaddingBottom = initialComputedStyles.getPropertyValue('padding-bottom');
101 cleanupExistingElementAnimation(element);
102 const targetHeight = element.getBoundingClientRect().height;
103 const computedStyles = getComputedStyle(element);
104 const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
105 const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
107 height: [`${startHeight}px`, `${targetHeight}px`],
108 overflow: ['hidden', 'hidden'],
109 paddingTop: [startPaddingTop, targetPaddingTop],
110 paddingBottom: [startPaddingBottom, targetPaddingBottom],
113 animateStyles(element, animStyles, animTime);
118 * Animate the css styles of an element using FLIP animation techniques.
119 * Styles must be an object where the keys are style properties, camelcase, and the values
120 * are an array of two items in the format [initialValue, finalValue]
121 * @param {Element} element
122 * @param {Object} styles
123 * @param {Number} animTime
124 * @param {Function} onComplete
126 function animateStyles(element, styles, animTime = 400, onComplete = null) {
127 const styleNames = Object.keys(styles);
128 for (let style of styleNames) {
129 element.style[style] = styles[style][0];
132 const cleanup = () => {
133 for (let style of styleNames) {
134 element.style[style] = null;
136 element.style.transition = null;
137 element.removeEventListener('transitionend', cleanup);
138 animateStylesCleanupMap.delete(element);
139 if (onComplete) onComplete();
143 element.style.transition = `all ease-in-out ${animTime}ms`;
144 for (let style of styleNames) {
145 element.style[style] = styles[style][1];
148 element.addEventListener('transitionend', cleanup);
149 animateStylesCleanupMap.set(element, cleanup);
154 * Run the active cleanup action for the given element.
155 * @param {Element} element
157 function cleanupExistingElementAnimation(element) {
158 if (animateStylesCleanupMap.has(element)) {
159 const oldCleanup = animateStylesCleanupMap.get(element);