simpleAnimation.js

const simpleTypeCheck = require('simple-type-check');
/** @define {boolean} */
const INCLUDE_DEBUG = true;
/**
* @typedef options
* @type {Object}
* @property {HTMLElement} target The DOM Node to animate.
* @property {animation[]} animations An array of the properties that will be animated, see Animations. At least one property required.
* @property {number=} [defaultDuration=250] The default animation duration.
* @property {string=} [defaultEasing=linear] The default CSS easing type.
* @property {boolean=} [DEBUG=false] Show debug messages. ⚠ Only avaible in the *.debug.js version of the module.
*/
/**
* @typedef animation
* @type {Object}
* @property {string} attribute The CSS Attribute to animate.
* @property {various} animateTo The value the element animates to.
* @property {number=} [duration=defaultDuration] The default CSS easing type.
* @property {string=} [easing=defaultEasing] The default CSS easing type.
* @property {boolean=} [pctToScroll=false] Converts `animateTo` percentage to pixel using `scrollHeight` and `scrollWidth`. Supported Attributes are: `width`/`min-widt`/`max-width`/`height`/`min-height`/`max-height`.
*/
/**
 * Get a decimal value from a percentage string.
 * @function
 * @param {string} value - The percentage as string.
 * @returns {number} the percentage as decimal number.
 */
const getDecimalFromPercentage = value => parseFloat(value) / 100;
/**
 * Check if the provided string contains height.
 * @function
 * @param {string} value - The string to test.
 * @returns {boolean} the result as boolean.
 */
const checkIfHeight = value => value.indexOf('height') !== -1;
/**
 * Check if the provided string contains width.
 * @function
 * @param {string} value - The string to test.
 * @returns {boolean} the result as boolean.
 */
const checkIfWidth = value => value.indexOf('width') !== -1;
/**
 * Convert miliseconds to seconds and add a `s` at the end.
 * @function
 * @param {number} value - The number to convert
 * @returns {string} the result as string with a `s` at the end.
 */
const msToSeconds = value => `${value / 1000}s`;
/**
 * Converts the given percentage to pixel equvalent.
 * @function
 * @param {HTMLElement} domTarget The DOM Node to get the height and width from.
 * @param {string} attribute CSS attribute to check if it contains height or width.
 * @param {string} animateTo The string with the end percentage.
 * @returns {string} the result as string with `px` added.
 */
const handlePercentage = (domTarget, attribute, animateTo, DEBUG) => {
    DEBUG && INCLUDE_DEBUG && console.info(`DEBUG: "pctToScroll" is true, convert "${animateTo}"`);
    const checkedHeight = checkIfHeight(attribute);
    const checkedWidth = checkIfWidth(attribute);
    if (!checkedHeight && !checkedWidth) {
        throw new Error(`Invalid direction: ${attribute}`);
    }
    const value = checkedHeight ?
        domTarget.scrollHeight :
        domTarget.scrollWidth;
    const pixel = `${value * getDecimalFromPercentage(animateTo)}px`;
    DEBUG && INCLUDE_DEBUG && console.info(`DEBUG: converted "${animateTo}" to "${pixel}" from attribute "${attribute}"`);
    return pixel;
};
/**
 * Parameters to call the module with.
 * @module simpleAnimation
 * @param {options} options The animation function
 */
const Main = (options) => {
    const {
        target,
        animations,
        defaultDuration = 250,
        defaultEasing = 'linear',
        DEBUG = false,
    } = options;
    DEBUG && INCLUDE_DEBUG && console.info('DEBUG: simpleAnimation startet with config:', options);
    
    simpleTypeCheck(target, window.Element);
    simpleTypeCheck(animations, Array);
    simpleTypeCheck(defaultDuration, 'number');
    simpleTypeCheck(defaultEasing, 'string');
    simpleTypeCheck(DEBUG, 'boolean');

    const transitions = [];
    const styles = [];
    animations.forEach((animation, index) => {
        DEBUG && INCLUDE_DEBUG && console.info(`DEBUG: process ${index + 1}. animation with options:`, animation);
        const {
            attribute,
            pctToScroll = false,
            duration = defaultDuration,
            easing = defaultEasing,
        } = animation;
        let {animateTo} = animation;

        simpleTypeCheck(attribute, 'string');
        simpleTypeCheck(pctToScroll, 'boolean');
        simpleTypeCheck(duration, 'number');
        simpleTypeCheck(easing, 'string');
        
        animateTo = pctToScroll ?
            handlePercentage(target, attribute, animateTo, DEBUG) :
            animateTo;

        styles.push({
            attribute,
            animateTo,
        });
        const transition = `${attribute} ${msToSeconds(duration)} ${easing}`;
        transitions.push(transition);
        DEBUG && INCLUDE_DEBUG && console.info(`DEBUG: animate "${attribute}" to "${animateTo}" with transition: ${transition}`);
    });
    const joinedTransitions = transitions.join();
    DEBUG && INCLUDE_DEBUG && console.info(`DEBUG: transitions to append: ${transitions.join()}`);
    target.style.transition = joinedTransitions;
    DEBUG && INCLUDE_DEBUG && console.info(`DEBUG: transitions now are: ${target.style.transition}`);
    styles.forEach((style) => {
        DEBUG && INCLUDE_DEBUG && console.info(`DEBUG: attribute to append: ${style.attribute}, value to append: ${style.animateTo}`);
        target.style[style.attribute] = style.animateTo;
        DEBUG && INCLUDE_DEBUG && console.info(`DEBUG: attribute "${style.attribute}" now is: ${target.style[style.attribute]}`);
    });
    /* istanbul ignore if */
    if (target.style.transition === '') {
        throw new Error(`Transitions invalid: ${transitions}`);
    }
};

module.exports = Main;