• Jump To … +
    ./source/core/animationloop.js ./source/core/document.js ./source/core/events.js ./source/core/init.js ./source/core/library.js ./source/core/random-seed.js ./source/core/snippets.js ./source/core/userInteraction.js ./source/core/utilities.js ./source/factory/action.js ./source/factory/anchor.js ./source/factory/animation.js ./source/factory/bezier.js ./source/factory/block.js ./source/factory/canvas.js ./source/factory/cell.js ./source/factory/cog.js ./source/factory/color.js ./source/factory/conicGradient.js ./source/factory/coordinate.js ./source/factory/crescent.js ./source/factory/element.js ./source/factory/emitter.js ./source/factory/filter.js ./source/factory/filterEngine-bluenoiseData.js ./source/factory/filterEngine.js ./source/factory/fontAttributes.js ./source/factory/gradient.js ./source/factory/grid.js ./source/factory/group.js ./source/factory/imageAsset.js ./source/factory/line.js ./source/factory/lineSpiral.js ./source/factory/loom.js ./source/factory/mesh.js ./source/factory/net.js ./source/factory/noiseAsset.js ./source/factory/oval.js ./source/factory/palette.js ./source/factory/particle.js ./source/factory/particleForce.js ./source/factory/particleHistory.js ./source/factory/particleSpring.js ./source/factory/particleWorld.js ./source/factory/pattern.js ./source/factory/phrase.js ./source/factory/picture.js ./source/factory/polygon.js ./source/factory/polyline.js ./source/factory/quadratic.js ./source/factory/quaternion.js ./source/factory/radialGradient.js ./source/factory/rawAsset.js ./source/factory/rdAsset.js ./source/factory/rectangle.js ./source/factory/renderAnimation.js ./source/factory/shape.js ./source/factory/spiral.js ./source/factory/spriteAsset.js ./source/factory/stack.js ./source/factory/star.js ./source/factory/state.js ./source/factory/tetragon.js ./source/factory/ticker.js ./source/factory/tracer.js ./source/factory/tween.js ./source/factory/unstackedElement.js ./source/factory/vector.js ./source/factory/videoAsset.js ./source/factory/wheel.js ./source/mixin/anchor.js ./source/mixin/asset.js ./source/mixin/assetAdvancedFunctionality.js ./source/mixin/assetConsumer.js ./source/mixin/base.js ./source/mixin/cascade.js ./source/mixin/delta.js ./source/mixin/displayShape.js ./source/mixin/dom.js ./source/mixin/entity.js ./source/mixin/filter.js ./source/mixin/mimic.js ./source/mixin/path.js ./source/mixin/pattern.js ./source/mixin/pivot.js ./source/mixin/position.js ./source/mixin/shapeBasic.js ./source/mixin/shapeCurve.js ./source/mixin/shapePathCalculation.js ./source/mixin/styles.js ./source/mixin/tween.js
  • §

    Core utility functions

    A ragtag collection of helper functions which other modules can import and use

  • §

    Imports

    import * as library from "./library.js";
  • §

    Functions

  • §

    addStrings adds the two arguments together and returns a percentage string value if either of the values was a string; or a sum of the two numbers.

    Examples:

    addStrings(20, 40);
    -> 60
       
    addStrings('20%', 40);
    -> '60%'
     
    addStrings(20, '40%');
    -> '60%'
     
    addStrings('20%', '40%');
    -> '60%'
     
    addStrings('center', 3);
    -> '53%'
    
    const addStrings = (current, delta) => {
    
        if (!xt(delta)) throw new Error(`core/utilities addStrings() error - no delta argument supplied ${current}, ${delta}`);
    
        if ((delta != null)) {
  • §

    Correct for labels

            if ('left' === current || 'top' === current) current = '0%';
            else if ('right' === current || 'bottom' === current) current = '100%';
            else if ('center' === current) current = '50%';
    
            let stringFlag = (current.substring || delta.substring) ? true : false;
    
            if (isa_number(current)) current += (isa_number(delta) ? delta : parseFloat(delta));
            else current = parseFloat(current) + (isa_number(delta) ? delta : parseFloat(delta));
    
            return (stringFlag) ? current + '%' : current;
        }
        return current;
    };
  • §

    convertTime converts a time value into its component string suffix and (millisecond) number value properties; returns an array

    Examples:

    convertTime(5000);
    -> ['ms', 5000]
       
    convertTime('50%');
    -> ['%', 50]
     
    convertTime('5000ms'); 
    -> ['ms', 5000]
     
    convertTime('5s');
    -> ['ms', 5000]
    
    const convertTime = (item) => {
    
        let a, timeUnit, timeValue;
    
        if (!xt(item)) throw new Error(`core/utilities convertTime() error - no argument supplied`);
    
        if (isa_number(item)) return ['ms', item];
    
        if (!item.substring) throw new Error(`core/utilities convertTime() error - invalid argument: ${item}`);
    
        a = item.match(/^\d+\.?\d*(\D*)/);
        timeUnit = (a[1].toLowerCase) ? a[1].toLowerCase() : 'ms';
        
        timeValue = parseFloat(item);
    
        if (!isa_number(timeValue)) throw new Error(`core/base error - convertTime() argument converts to NaN: ${item}`);
    
        switch (timeUnit) {
    
            case 's':
                timeValue *= 1000;
                break;
    
            case '%':
                break;
    
            default:
                timeUnit = 'ms';
        }
        
        return [timeUnit, timeValue];
    };
  • §

    correctAngle makes sure any degree-based angle is in the range 0-360

    const correctAngle = (item) => {
    
        if (!item.toFixed || isNaN(item)) {
    
            console.log(`${item} is not a valid number`);
            return item;
        }
    
        item = item % 360;
    
        if (item < 0) {
            item += 360;
        }
        return item;
    };
  • §

    correctForZero checks and corrects for minor deviations from zero (eNumbers)

    const correctForZero = (item) => {
    
        if (!item.toFixed) return item;
        if (item == 0) return item;
        if (isNaN(item)) return 0;
        if (item < -0.000001) return item;
        if (item > 0.000001) return item;
        return 0;
    };
  • §

    λ functions helps us avoid errors when invoking a function attribute settable by the coder

    const λnull = () => {};
    const λfirstArg = function (a) { return a; };
    const λthis = function () { return this; };
    const λpromise = () => Promise.resolve(true);
    const Ωempty = {};
  • §

    generateUuid is a simple (crude) uuid generator http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript (imported 2017-07-08)

    const generateUuid = () => {
    
        function s4() {
    
            return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
        }
    
        return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`;
    };
  • §

    generateUniqueString is a simple random String generator https://gist.github.com/SimonHoiberg/ad2710c8626c5a74cddd8f6385795cc0 (imported 2020-11-22)

    const generateUniqueString = () => {
    
        return performance.now().toString(36) + Math.random().toString(36).substr(2);
    };
  • §

    interpolate clamp a value between a maximum and minimum value

    const interpolate = function (val, min, max) {
        
        return min + val * (max - min);
    };
  • §

    isa_boolean checks to make sure the argument is a boolean

    const isa_boolean = item => (typeof item === 'boolean') ? true : false;
  • §

    isa_canvas checks to make sure the argument is a DOM <canvas> element

    const isa_canvas = item => (Object.prototype.toString.call(item) === '[object HTMLCanvasElement]') ? true : false;
  • §

    isa_dom checks to make sure the argument is a DOM element of some sort

    const isa_dom = item => (item && item.querySelector && item.dispatchEvent) ? true : false;
  • §

    isa_fn checks to make sure the argument is a JavaScript function object

    const isa_fn = item => (typeof item === 'function') ? true : false;
  • §

    isa_number checks to make sure the argument is true number (excluding NaN)

    const isa_number = item => (item != null && item.toFixed && !Number.isNaN(item)) ? true : false;
  • §

    isa_obj checks to make sure the argument is a JavaScript Object

    const isa_obj = item => (Object.prototype.toString.call(item) === '[object Object]') ? true : false;
  • §

    isa_quaternion checks to make sure the argument is a Scrawl-canvas Quaternion object

    const isa_quaternion = item => (item && item.type && item.type === 'Quaternion') ? true : false;
  • §

    mergeInto takes two objects and merges the attributes of one into the other. This function mutates the ‘original’ object rather than generating a third, new onject

    Example:

    var original = { name: 'Peter', age: 42, job: 'lawyer' };
    var additional = { age: 32, job: 'coder', pet: 'cat' };
    scrawl.utils.mergeInto(original, additional);
    
    -> { name: 'Peter', age: 42, job: 'lawyer', pet: 'cat' }
    
    const mergeInto = (original, additional) => {
    
        if (!isa_obj(original) || !isa_obj(additional)) throw new Error(`core/utilities mergeInto() error - insufficient arguments supplied ${original}, ${additional}`);
    
        for (let key in additional) {
    
            if (additional.hasOwnProperty(key) && typeof original[key] == 'undefined') original[key] = additional[key];
        }
        return original;
    };
  • §

    mergeOver takes two objects and writes the attributes of one over the other. This function mutates the ‘original’ object rather than generating a third, new onject

    Example:

    var original = { name: 'Peter', age: 42, job: 'lawyer' };
    var additional = { age: 32, job: 'coder', pet: 'cat' };
    scrawl.utils.mergeOver(original, additional);
    
    -> { name: 'Peter', age: 32, job: 'coder', pet: 'cat' }
    
    const mergeOver = (original, additional) => {
    
        if (!isa_obj(original) || !isa_obj(additional)) throw new Error(`core/utilities mergeOver() error - insufficient arguments supplied ${original}, ${additional}`);
    
        for (let key in additional) {
    
            if (additional.hasOwnProperty(key)) original[key] = additional[key];
        }
        return original;
    };
  • §

    mergeDiscard iterates over the additional object to perform a mergeOver operation, and also removing attributes from the original object where they have been set to null in the additional object

    Example:

    var original = { name: 'Peter', age: 42, job: 'lawyer' };
    var additional = { age: 32, job: null, pet: 'cat' };
    scrawl.utils.mergeOver(original, additional);
       
    -> { name: 'Peter', age: 32, pet: 'cat' }
    
    const mergeDiscard = (original, additional) => {
    
        if (!isa_obj(original) || !isa_obj(additional)) throw new Error(`core/utilities mergeDiscard() error - insufficient arguments supplied ${original}, ${additional}`);
    
        Object.entries(additional).forEach(([key, val]) => {
    
            if (val === null) delete original[key];
            else original[key] = additional[key];
        });
        return original;
    };
  • §

    pushUnique adds a value to the end of an array, if that value is not already present in the array. This function mutates the array

    Example:

    var myarray = ['apple', 'orange'];
    scrawl.utils.pushUnique(myarray, 'apple');    
    -> ['apple', 'orange']
    
    scrawl.utils.pushUnique(myarray, 'banana');
    -> ['apple', 'orange', 'banana']
    
    const pushUnique = (myArray, potentialMember) => {
    
        if (!xta(myArray, potentialMember)) throw new Error(`core/utilities pushUnique() error - insufficient arguments supplied ${myArray}, ${potentialMember}`);
    
        if (!Array.isArray(myArray)) throw new Error(`core/utilities pushUnique() error - argument not an array ${myArray}`);
    
        if (Array.isArray(potentialMember)) {
    
            potentialMember.forEach(item => pushUnique(myArray, item));
        }
        else {
    
            if (myArray.indexOf(potentialMember) < 0) myArray.push(potentialMember);
        }
    
        return myArray;
    };
  • §

    removeItem removes a value from an array. This function mutates the array

    Example:

    var myarray = ['apple', 'orange'];
    scrawl.utils.removeItem(myarray, 'banana');   
    -> ['apple', 'orange']
    
    scrawl.utils.removeItem(myarray, 'apple');    
    -> ['orange']
    
    const removeItem = (myArray, unwantedMember) => {
    
        if (!xta(myArray, unwantedMember)) throw new Error(`core/utilities removeItem() error - insufficient arguments supplied ${myArray}, ${unwantedMember}`);
    
        if (!Array.isArray(myArray)) throw new Error(`core/utilities removeItem() error - argument not an array ${myArray}`);
    
        let index = myArray.indexOf(unwantedMember);
    
        if (index >= 0) myArray.splice(index, 1);
    
        return myArray;
    };
  • §

    xt checks to see if argument exists (is not ‘undefined’)

    const xt = item => (typeof item == 'undefined') ? false : true;
  • §

    xta checks to make sure that all the arguments supplied to the function exist (none are ‘undefined’)

    const xta = (...args) => args.every(item => typeof item != 'undefined');
  • §

    xtGet returns the first existing (not ‘undefined’) argument supplied to the function

    const xtGet = (...args) => args.find(item => typeof item != 'undefined');
  • §

    xto checks to make sure that at least one of the arguments supplied to the function exists (is not ‘undefined’)

    const xto = (...args) => (args.find(item => typeof item != 'undefined')) ? true : false;
  • §
    Easing engines

    easeEngines - an object in which keys define various easing functions

    const easeEngines = {
  • §

    Legacy easings

        out: (t) => 1 - Math.cos((t * Math.PI) / 2),
        in: (t) => Math.sin((t * Math.PI) / 2),
        easeIn: (t) => {
            
            let temp = 1 - t;
            return 1 - (temp * temp);
        },
        easeIn3: (t) => {
    
            let temp = 1 - t;
            return 1 - (temp * temp * temp);
        },
        easeIn4: (t) => {
    
            let temp = 1 - t;
            return 1 - (temp * temp * temp * temp);
        },
        easeIn5: (t) => {
    
            let temp = 1 - t;
            return 1 - (temp * temp * temp * temp * temp);
        },
        easeOutIn: (t) => (t < 0.5) ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2,
        easeOutIn3: (t) => (t < 0.5) ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
        easeOutIn4: (t) => (t < 0.5) ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2,
        easeOutIn5: (t) => (t < 0.5) ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2,
    
        easeInOut: (t) => {
    
            const tin = 0.5 - t,
                tout = t - 0.5;
    
            if (t < 0.5) return 0.5 + (-2 * tin * tin);
            return 0.5 + Math.pow(2 * tout, 2) / 2;
        },
        easeInOut3: (t) => {
    
            const tin = 0.5 - t,
                tout = t - 0.5;
    
            if (t < 0.5) return 0.5 + (-4 * tin * tin * tin);
            return 0.5 + Math.pow(2 * tout, 3) / 2;
        },
        easeInOut4: (t) => {
    
            const tin = 0.5 - t,
                tout = t - 0.5;
    
            if (t < 0.5) return 0.5 + (-8 * tin * tin * tin * tin);
            return 0.5 + Math.pow(2 * tout, 4) / 2;
        },
        easeInOut5: (t) => {
    
            const tin = 0.5 - t,
                tout = t - 0.5;
    
            if (t < 0.5) return 0.5 + (-16 * tin * tin * tin * tin * tin);
            return 0.5 + Math.pow(2 * tout, 5) / 2;
        },
    
        easeOut: (t) => t * t,
        easeOut3: (t) => t * t * t,
        easeOut4: (t) => t * t * t * t,
        easeOut5: (t) => t * t * t * t * t,
    
        none: (val) => val,
        linear: (val) => val,
  • §

    Noise functionality easing engines

        cosine: (t) => .5 * (1 + Math.cos((1 - t) * Math.PI)),
        hermite: (t) => t * t * (-t * 2 + 3),
        quintic: (t) => t * t * t * (t * (t * 6 - 15) + 10),
  • §

    The following easing variations come from the easings.net web page

    • Note: the naming convention for easing is different in Scrawl-canvas. Easing out implies a speeding up, while easing in implies a slowing down. Think of a train easing into a station, and then easing out of it again as it continues its journey.
        easeOutSine: (t) => 1 - Math.cos((t * Math.PI) / 2),
        easeInSine: (t) => Math.sin((t * Math.PI) / 2),
        easeOutInSine: (t) => -(Math.cos(Math.PI * t) - 1) / 2,
    
        easeOutQuad: (t) => t * t,
        easeInQuad: (t) => 1 - ((1 - t) * (1 - t)),
        easeOutInQuad: (t) => (t < 0.5) ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2,
    
        easeOutCubic: (t) => t * t * t,
        easeInCubic: (t) => 1 - Math.pow(1 - t, 3),
        easeOutInCubic: (t) => (t < 0.5) ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
    
        easeOutQuart: (t) => t * t * t * t,
        easeInQuart: (t) => 1 - Math.pow(1 - t, 4),
        easeOutInQuart: (t) => (t < 0.5) ? 8 * t * t * t * t : 1 - Math.pow(-2 * t + 2, 4) / 2,
    
        easeOutQuint: (t) => t * t * t * t * t,
        easeInQuint: (t) => 1 - Math.pow(1 - t, 5),
        easeOutInQuint: (t) => (t < 0.5) ? 16 * t * t * t * t * t : 1 - Math.pow(-2 * t + 2, 5) / 2,
    
        easeOutExpo: (t) => (t === 0) ? 0 : Math.pow(2, 10 * t - 10),
        easeInExpo: (t) => (t === 1) ? 1 : 1 - Math.pow(2, -10 * t),
        easeOutInExpo: (t) => {
            if (t === 0 || t === 1) return t;
            return t < 0.5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2;
        },
    
        easeOutCirc: (t) => 1 - Math.sqrt(1 - Math.pow(t, 2)),
        easeInCirc: (t) => Math.sqrt(1 - Math.pow(t - 1, 2)),
        easeOutInCirc: (t) => { 
    
            if (t < 0.5) return (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2;
            return (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2;
        },
    
        easeOutBack: (t) => (2.70158 * t * t * t) - (1.70158 * t * t),
        easeInBack: (t) => 1 + (2.70158 * Math.pow(t - 1, 3)) + (1.70158 * Math.pow(t - 1, 2)),
        easeOutInBack: (t) => {
    
            const c1 = 1.70158, c2 = c1 * 1.525;
            if (t < 0.5) return (Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2;
            return (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
        },
    
        easeOutElastic: (t) => {
    
            const c4 = (2 * Math.PI) / 3;
            if (t === 0 || t === 1) return t;
            return -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * c4);
        },
        easeInElastic: (t) => {
    
            const c4 = (2 * Math.PI) / 3;
            if (t === 0 || t === 1) return t;
            return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
        },
        easeOutInElastic: (t) => {
    
            const c5 = (2 * Math.PI) / 4.5;
            if (t === 0 || t === 1) return t;
            if (t < 0.5) return -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2;
            return (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5)) / 2 + 1;
        },
    
        easeOutBounce: (t) => {
    
            t = 1 - t;
    
            const n1 = 7.5625, 
                d1 = 2.75;
    
            if (t < 1 / d1) return 1 - (n1 * t * t);
            if (t < 2 / d1) return 1 - (n1 * (t -= 1.5 / d1) * t + 0.75);
            if (t < 2.5 / d1) return 1 - (n1 * (t -= 2.25 / d1) * t + 0.9375);
            return 1 - (n1 * (t -= 2.625 / d1) * t + 0.984375);
        },
    
        easeInBounce: (t) => {
    
            const n1 = 7.5625, 
                d1 = 2.75;
    
            if (t < 1 / d1) return n1 * t * t;
            if (t < 2 / d1) return n1 * (t -= 1.5 / d1) * t + 0.75;
            if (t < 2.5 / d1) return n1 * (t -= 2.25 / d1) * t + 0.9375;
            return n1 * (t -= 2.625 / d1) * t + 0.984375;
        },
  • §

    This is wrong

        easeOutInBounce: (t) => {
    
            const n1 = 7.5625, d1 = 2.75;
            let res;
    
            if (t < 0.5) {
                t = 1 - 2 * t;
                if (t < 1 / d1) res = n1 * t * t;
                else if (t < 2 / d1) res = n1 * (t -= 1.5 / d1) * t + 0.75;
                else if (t < 2.5 / d1) res = n1 * (t -= 2.25 / d1) * t + 0.9375;
                else res = n1 * (t -= 2.625 / d1) * t + 0.984375;
                return (1 - res) / 2;
            }
            else {
                t = 2 * t - 1;
                if (t < 1 / d1) res = n1 * t * t;
                else if (t < 2 / d1) res = n1 * (t -= 1.5 / d1) * t + 0.75;
                else if (t < 2.5 / d1) res = n1 * (t -= 2.25 / d1) * t + 0.9375;
                else res = n1 * (t -= 2.625 / d1) * t + 0.984375;
                return (1 + res) / 2;
            }
        },
    };
    
    
    const getArrayType = someObject => someObject && someObject.constructor && someObject.constructor.name && someObject.constructor.name || null;
  • §

    Code to detect browser from Code Box: https://code-boxx.com/detect-browser-with-javascript/

    • Because there’s times when it’s easier to just shut down features in webkit-based browsers than deal with their nonsense (looking at you: Safari!)
    const detectBrowser = function () {
    
        let result = [];
    
        if ((!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0) result.push('old-opera');
    
        if (typeof InstallTrigger !== 'undefined') result.push('firefox');
    
        else if (/constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification))) result.push('safari');
    
        if (/*@cc_on!@*/false || !!document.documentMode) result.push('internet-explorer');
    
        if (result.indexOf('internet-explorer') < 0 && !!window.StyleMedia) result.push('edge');
    
        if(!!window.chrome) result.push('chrome');
    
        if((result.indexOf('chrome') >= 0 || result.indexOf('old-opera') >= 0) && !!window.CSS) result.push('blink');
    
        return result;
    };
  • §

    Exports

    export {
        addStrings,
        convertTime,
        correctAngle,
        correctForZero,
        λnull,
        λfirstArg,
        λthis,
        λpromise,
        Ωempty,
        generateUuid,
        generateUniqueString,
        interpolate,
        isa_boolean,
        isa_canvas,
        isa_dom,
        isa_fn,
        isa_number,
        isa_obj,
        isa_quaternion,
        mergeDiscard,
        mergeInto,
        mergeOver,
        pushUnique,
        removeItem,
        xt,
        xta,
        xtGet,
        xto, 
    
        easeEngines,
    
        getArrayType,
    
        detectBrowser,
    };