]> BookStack Code Mirror - bookstack/blob - resources/assets/js/services/animations.js
5cb90b70c77cf13048ae82a4ec64df56977e58f0
[bookstack] / resources / assets / js / services / animations.js
1 /**
2  * Hide the element by sliding the contents upwards.
3  * @param {Element} element
4  * @param {Number} animTime
5  */
6 export function slideUp(element, animTime = 400) {
7     const currentHeight = element.getBoundingClientRect().height;
8     const computedStyles = getComputedStyle(element);
9     const currentPaddingTop = computedStyles.getPropertyValue('padding-top');
10     const currentPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
11     const animStyles = {
12         height: [`${currentHeight}px`, '0px'],
13         overflow: ['hidden', 'hidden'],
14         paddingTop: [currentPaddingTop, '0px'],
15         paddingBottom: [currentPaddingBottom, '0px'],
16     };
17
18     animateStyles(element, animStyles, animTime, () => {
19         element.style.display = 'none';
20     });
21 }
22
23 /**
24  * Show the given element by expanding the contents.
25  * @param {Element} element - Element to animate
26  * @param {Number} animTime - Animation time in ms
27  */
28 export function slideDown(element, animTime = 400) {
29     element.style.display = 'block';
30     const targetHeight = element.getBoundingClientRect().height;
31     const computedStyles = getComputedStyle(element);
32     const targetPaddingTop = computedStyles.getPropertyValue('padding-top');
33     const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom');
34     const animStyles = {
35         height: ['0px', `${targetHeight}px`],
36         overflow: ['hidden', 'hidden'],
37         paddingTop: ['0px', targetPaddingTop],
38         paddingBottom: ['0px', targetPaddingBottom],
39     };
40
41     animateStyles(element, animStyles, animTime);
42 }
43
44 /**
45  * Used in the function below to store references of clean-up functions.
46  * Used to ensure only one transitionend function exists at any time.
47  * @type {WeakMap<object, any>}
48  */
49 const animateStylesCleanupMap = new WeakMap();
50
51 /**
52  * Animate the css styles of an element using FLIP animation techniques.
53  * Styles must be an object where the keys are style properties, camelcase, and the values
54  * are an array of two items in the format [initialValue, finalValue]
55  * @param {Element} element
56  * @param {Object} styles
57  * @param {Number} animTime
58  * @param {Function} onComplete
59  */
60 function animateStyles(element, styles, animTime = 400, onComplete = null) {
61     const styleNames = Object.keys(styles);
62     for (let style of styleNames) {
63         element.style[style] = styles[style][0];
64     }
65
66     const cleanup = () => {
67         for (let style of styleNames) {
68             element.style[style] = null;
69         }
70         element.style.transition = null;
71         element.removeEventListener('transitionend', cleanup);
72         if (onComplete) onComplete();
73     };
74
75     setTimeout(() => {
76         requestAnimationFrame(() => {
77             element.style.transition = `all ease-in-out ${animTime}ms`;
78             for (let style of styleNames) {
79                 element.style[style] = styles[style][1];
80             }
81
82             if (animateStylesCleanupMap.has(element)) {
83                 const oldCleanup = animateStylesCleanupMap.get(element);
84                 element.removeEventListener('transitionend', oldCleanup);
85             }
86
87             element.addEventListener('transitionend', cleanup);
88             animateStylesCleanupMap.set(element, cleanup);
89         });
90     }, 10);
91 }