X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/ddb7f33868ea499ab8f48a7062f145e8c0fbe02f..refs/pull/5280/head:/resources/js/services/animations.js diff --git a/resources/js/services/animations.js b/resources/js/services/animations.js index b6158ea5f..bc983c807 100644 --- a/resources/js/services/animations.js +++ b/resources/js/services/animations.js @@ -5,6 +5,69 @@ */ const animateStylesCleanupMap = new WeakMap(); +/** + * Animate the css styles of an element using FLIP animation techniques. + * Styles must be an object where the keys are style properties, camelcase, and the values + * are an array of two items in the format [initialValue, finalValue] + * @param {Element} element + * @param {Object} styles + * @param {Number} animTime + * @param {Function} onComplete + */ +function animateStyles(element, styles, animTime = 400, onComplete = null) { + const styleNames = Object.keys(styles); + for (const style of styleNames) { + element.style[style] = styles[style][0]; + } + + const cleanup = () => { + for (const style of styleNames) { + element.style[style] = null; + } + element.style.transition = null; + element.removeEventListener('transitionend', cleanup); + animateStylesCleanupMap.delete(element); + if (onComplete) onComplete(); + }; + + setTimeout(() => { + element.style.transition = `all ease-in-out ${animTime}ms`; + for (const style of styleNames) { + element.style[style] = styles[style][1]; + } + + element.addEventListener('transitionend', cleanup); + animateStylesCleanupMap.set(element, cleanup); + }, 15); +} + +/** + * Run the active cleanup action for the given element. + * @param {Element} element + */ +function cleanupExistingElementAnimation(element) { + if (animateStylesCleanupMap.has(element)) { + const oldCleanup = animateStylesCleanupMap.get(element); + oldCleanup(); + } +} + +/** + * Fade in the given element. + * @param {Element} element + * @param {Number} animTime + * @param {Function|null} onComplete + */ +export function fadeIn(element, animTime = 400, onComplete = null) { + cleanupExistingElementAnimation(element); + element.style.display = 'block'; + animateStyles(element, { + opacity: ['0', '1'], + }, animTime, () => { + if (onComplete) onComplete(); + }); +} + /** * Fade out the given element. * @param {Element} element @@ -14,7 +77,7 @@ const animateStylesCleanupMap = new WeakMap(); export function fadeOut(element, animTime = 400, onComplete = null) { cleanupExistingElementAnimation(element); animateStyles(element, { - opacity: ['1', '0'] + opacity: ['1', '0'], }, animTime, () => { element.style.display = 'none'; if (onComplete) onComplete(); @@ -33,7 +96,7 @@ export function slideUp(element, animTime = 400) { const currentPaddingTop = computedStyles.getPropertyValue('padding-top'); const currentPaddingBottom = computedStyles.getPropertyValue('padding-bottom'); const animStyles = { - height: [`${currentHeight}px`, '0px'], + maxHeight: [`${currentHeight}px`, '0px'], overflow: ['hidden', 'hidden'], paddingTop: [currentPaddingTop, '0px'], paddingBottom: [currentPaddingBottom, '0px'], @@ -57,7 +120,7 @@ export function slideDown(element, animTime = 400) { const targetPaddingTop = computedStyles.getPropertyValue('padding-top'); const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom'); const animStyles = { - height: ['0px', `${targetHeight}px`], + maxHeight: ['0px', `${targetHeight}px`], overflow: ['hidden', 'hidden'], paddingTop: ['0px', targetPaddingTop], paddingBottom: ['0px', targetPaddingBottom], @@ -67,48 +130,33 @@ export function slideDown(element, animTime = 400) { } /** - * Animate the css styles of an element using FLIP animation techniques. - * Styles must be an object where the keys are style properties, camelcase, and the values - * are an array of two items in the format [initialValue, finalValue] - * @param {Element} element - * @param {Object} styles - * @param {Number} animTime - * @param {Function} onComplete + * Transition the height of the given element between two states. + * Call with first state, and you'll receive a function in return. + * Call the returned function in the second state to animate between those two states. + * If animating to/from 0-height use the slide-up/slide down as easier alternatives. + * @param {Element} element - Element to animate + * @param {Number} animTime - Animation time in ms + * @returns {function} - Function to run in second state to trigger animation. */ -function animateStyles(element, styles, animTime = 400, onComplete = null) { - const styleNames = Object.keys(styles); - for (let style of styleNames) { - element.style[style] = styles[style][0]; - } - - const cleanup = () => { - for (let style of styleNames) { - element.style[style] = null; - } - element.style.transition = null; - element.removeEventListener('transitionend', cleanup); - animateStylesCleanupMap.delete(element); - if (onComplete) onComplete(); - }; +export function transitionHeight(element, animTime = 400) { + const startHeight = element.getBoundingClientRect().height; + const initialComputedStyles = getComputedStyle(element); + const startPaddingTop = initialComputedStyles.getPropertyValue('padding-top'); + const startPaddingBottom = initialComputedStyles.getPropertyValue('padding-bottom'); - setTimeout(() => { - element.style.transition = `all ease-in-out ${animTime}ms`; - for (let style of styleNames) { - element.style[style] = styles[style][1]; - } + return () => { + cleanupExistingElementAnimation(element); + const targetHeight = element.getBoundingClientRect().height; + const computedStyles = getComputedStyle(element); + const targetPaddingTop = computedStyles.getPropertyValue('padding-top'); + const targetPaddingBottom = computedStyles.getPropertyValue('padding-bottom'); + const animStyles = { + height: [`${startHeight}px`, `${targetHeight}px`], + overflow: ['hidden', 'hidden'], + paddingTop: [startPaddingTop, targetPaddingTop], + paddingBottom: [startPaddingBottom, targetPaddingBottom], + }; - element.addEventListener('transitionend', cleanup); - animateStylesCleanupMap.set(element, cleanup); - }, 15); + animateStyles(element, animStyles, animTime); + }; } - -/** - * Run the active cleanup action for the given element. - * @param {Element} element - */ -function cleanupExistingElementAnimation(element) { - if (animateStylesCleanupMap.has(element)) { - const oldCleanup = animateStylesCleanupMap.get(element); - oldCleanup(); - } -} \ No newline at end of file