/**
* @module animate
* @description Animate dom elements using the web animation api. https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API
* This set of animations is just a few opinionated animations which currently include things like fading or translateY.
* You can either run them on a group, or individually. Configuration can be passed directly, or applied to data attributes on the target element.
* Data attributes to be placed on the element if not using the options object are: `data-animation-delay`, `data-animation-duration`, `data-animation-easing`, `data-animation-types`, `data-translate-distance-from`, `data-translate-distance-to`.
*
* @since 1.5.0
*
*/
/**
* @function getAnimationKeyframes
* @description Get the keyframes for the animation. Follows this format https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Keyframe_Formats
*
* @since 1.5.0
*
* @param {HTMLElement} target The target element to animate.
* @param {object} options The options for the animation that are needed to form the Keyframe object.
* @param {string} options.distanceFrom The distance to travel for translateY if supplied as type.
* @param {string} options.distanceTo The distance to travel for translateY if supplied as type.
* @param {string} options.opacityFrom The opacity value to animate from for fadeIn or fadeOut types.
* @param {string} options.opacityTo The opacity value to animate to for fadeIn or fadeOut types.
* @param {string} options.types The types of animations to run. Supports `fadeIn`, `fadeOut`, and `translateY`. You can mix them as space seperated values.
*
* @return {Array} The Keyframe objects.
*/
const getAnimationKeyframes = ( target, options ) => {
const from = {};
const to = {};
const {
distanceFrom = target.dataset?.translateDistanceFrom || '20px',
distanceTo = target.dataset?.translateDistanceTo || '0px',
opacityFrom = target.dataset?.translateOpacityFrom,
opacityTo = target.dataset?.translateOpacityTo,
types = target.dataset?.animationTypes || '',
} = options;
types.split( ' ' ).forEach( ( type ) => {
if ( type === 'fadeIn' ) {
from.opacity = opacityFrom || 0;
to.opacity = opacityTo || 1;
}
if ( type === 'fadeOut' ) {
from.opacity = opacityFrom || 1;
to.opacity = opacityTo || 0;
}
if ( type === 'translateY' ) {
from.transform = `translateY(${ distanceFrom })`;
to.transform = `translateY(${ distanceTo })`;
}
} );
return [ from, to ];
};
/**
* @function end
* @description End the animation.
*
* @since 1.5.0
*
* @param {HTMLElement} target The target element to animate.
* @param {object} options The options for the animation that are needed to determine what type of properties are applied to the element at the end of the animation.
* @param {string} options.distanceTo The distance to end the translateY animation at if supplied as type.
* @param {string} options.opacityTo The opacity value to animate to for fadeIn or fadeOut types.
* @param {string} options.types The types of animations to handle. Supports `fadeIn`, `fadeOut`, and `translateY`.
*/
const end = ( target, options ) => {
const {
distanceTo = target.dataset?.translateDistanceTo || '0px',
opacityTo = target.dataset?.translateOpacityTo,
types = target.dataset?.animationTypes || '',
} = options;
types.split( ' ' ).forEach( ( type ) => {
if ( type === 'fadeIn' ) {
target.style.opacity = opacityTo || '1';
target.setAttribute( 'aria-hidden', 'false' );
}
if ( type === 'fadeOut' ) {
target.style.opacity = opacityTo || '0';
target.setAttribute( 'aria-hidden', 'true' );
}
if ( type === 'translateY' ) {
target.style.transform = `translateY(${ distanceTo })`;
}
} );
};
/**
* @function run
* @description Animate a single element.
*
* @since 1.5.0
*
* @param {HTMLElement} target The target element to animate.
* @param {object} options The options for the animation that are needed to determine what type of properties are applied to the element at the end of the animation.
* @param {Function} options.onAnimateInit Function to run when initializing animation.
* @param {Function} options.onAnimateStart Function to run at the beginning of the animation.
* @param {Function} options.onAnimateEnd Function to run at the end of the animation.
* @param {number} options.delay Delay for the animation in milliseconds.
* @param {string|number} options.distance The distance to travel in pixels for translateY if supplied as type.
* @param {number} options.duration Duration for the animation in milliseconds.
* @param {string} options.easing Easing for the animation. Supports `ease`, `ease-in`, `ease-out`, `ease-in-out`, `linear`, `step-start`, `step-end`, `steps()`, `cubic-bezier()`.
* @param {string} options.types The types of animations to run. Supports `fadeIn`, `fadeOut`, and `translateY`. You can mix them as space seperated values.
*
* @return {void}
* @example
* import { animate } from '@gravityforms/utils';
*
* const animateExample = () => {
* const logo = getNode( '.gform-splash__header .gform-logo', document, true );
*
* animate.run( logo, {
* types: 'fadeIn translateY',
* distanceFrom: '-50px',
* distanceTo: '0px',
* duration: 1000,
* easing: 'ease-in-out',
* delay: 500,
* onInit: () => {
* console.log( 'Animation initializing!' );
* },
* onAnimateStart: () => {
* console.log( 'Animation starting!' );
* },
* onAnimateEnd: () => {
* console.log( 'Animation complete!' );
* },
* } );
* };
*
*/
const run = ( target = null, options = {} ) => {
if ( ! target ) {
return;
}
const {
onAnimateInit = () => {},
onAnimateStart = () => {},
onAnimateEnd = () => {},
delay = target.dataset?.animationDelay || 0,
duration = target.dataset?.animationDuration || 400,
easing = target.dataset?.animationEasing || 'linear',
} = options;
const keyframes = getAnimationKeyframes( target, options );
onAnimateInit();
setTimeout( () => {
onAnimateStart();
requestAnimationFrame( () => {
const animated = target.animate( keyframes, {
duration: Number( duration ),
easing,
} );
animated.onfinish = () => {
end( target, options );
onAnimateEnd();
};
} );
}, delay );
};
/**
* @function runGroup
* @description Animate a group of elements.
*
* @since 1.5.0
*
* @param {Array} entries The elements to animate. An array containing a target entry and an options entry that follows the structure of the run function options object.
*
* @return {void}
* @example
* import { animate } from '@gravityforms/utils';
*
* const animateExample = () => {
* const logo = getNode( '.gform-splash__header .gform-logo', document, true );
* const heading = getNode( '.gform-splash__header h1', document, true );
* const text = getNode( '.gform-splash__header p', document, true );
* const button = getNode( '.gform-splash__header .gform-button', document, true );
* const reviews = getNode( '.gform-splash__header .gform-reviews', document, true );
*
* const defaults = {
* onAnimateEnd: () => {
* console.log( `hello from: ${ performance.now() }` );
* },
* distance: -20,
* duration: 600,
* easing: 'cubic-bezier(0.455, 0.030, 0.515, 0.955)',
* types: 'fadeIn translateY',
* };
* animate.runGroup( [
* { target: logo, options: { ...defaults } },
* { target: heading, options: { callback: defaults.callback } }, // this one uses data attributes or internal defaults instead since no options where passed here.
* { target: text, options: { ...defaults, delay: 200 } },
* { target: button, options: { ...defaults, delay: 300 } },
* { target: reviews, options: { ...defaults, delay: 400 } },
* ] );
* };
*
*/
const runGroup = ( entries = [] ) => {
entries.forEach( ( { target, options } ) => {
run( target, options );
} );
};
export { run, runGroup };