• Jump To … +
    ./source/asset-management/image-asset.js ./source/asset-management/noise-asset.js ./source/asset-management/raw-asset.js ./source/asset-management/reaction-diffusion-asset.js ./source/asset-management/sprite-asset.js ./source/asset-management/video-asset.js ./source/core/animation-loop.js ./source/core/display-cycle.js ./source/core/document.js ./source/core/events.js ./source/core/init.js ./source/core/library.js ./source/core/snippets.js ./source/core/user-interaction.js ./source/factory/action.js ./source/factory/anchor.js ./source/factory/animation.js ./source/factory/bezier.js ./source/factory/block.js ./source/factory/button.js ./source/factory/canvas.js ./source/factory/cell.js ./source/factory/cog.js ./source/factory/color.js ./source/factory/conic-gradient.js ./source/factory/crescent.js ./source/factory/element.js ./source/factory/emitter.js ./source/factory/enhanced-label.js ./source/factory/filter.js ./source/factory/gradient.js ./source/factory/grid.js ./source/factory/group.js ./source/factory/label.js ./source/factory/line-spiral.js ./source/factory/line.js ./source/factory/loom.js ./source/factory/mesh.js ./source/factory/net.js ./source/factory/oval.js ./source/factory/particle-force.js ./source/factory/particle-spring.js ./source/factory/particle-world.js ./source/factory/particle.js ./source/factory/pattern.js ./source/factory/picture.js ./source/factory/polygon.js ./source/factory/polyline.js ./source/factory/quadratic.js ./source/factory/radial-gradient.js ./source/factory/rectangle.js ./source/factory/render-animation.js ./source/factory/shape.js ./source/factory/spiral.js ./source/factory/stack.js ./source/factory/star.js ./source/factory/tetragon.js ./source/factory/ticker.js ./source/factory/tracer.js ./source/factory/tween.js ./source/factory/unstacked-element.js ./source/factory/wheel.js ./source/helper/array-pool.js ./source/helper/color-engine.js ./source/helper/document-root-elements.js ./source/helper/filter-engine-bluenoise-data.js ./source/helper/filter-engine.js ./source/helper/gradient-engine.js ./source/helper/random-seed.js ./source/helper/shape-path-calculation.js ./source/helper/shared-vars.js ./source/helper/system-flags.js ./source/helper/utilities.js ./source/helper/workstore.js ./source/mixin/anchor.js ./source/mixin/asset-advanced-functionality.js ./source/mixin/asset-consumer.js ./source/mixin/asset.js ./source/mixin/base.js ./source/mixin/button.js ./source/mixin/cascade.js ./source/mixin/cell-key-functions.js ./source/mixin/delta.js ./source/mixin/display-shape.js ./source/mixin/dom.js ./source/mixin/entity.js ./source/mixin/filter.js ./source/mixin/hidden-dom-elements.js ./source/mixin/mimic.js ./source/mixin/path.js ./source/mixin/pattern.js ./source/mixin/pivot.js ./source/mixin/position.js ./source/mixin/shape-basic.js ./source/mixin/shape-curve.js ./source/mixin/styles.js ./source/mixin/text.js ./source/mixin/tween.js ./source/scrawl.js ./source/untracked-factory/cell-fragment.js ./source/untracked-factory/coordinate.js ./source/untracked-factory/drag-zone.js ./source/untracked-factory/keyboard-zone.js ./source/untracked-factory/observe-update.js ./source/untracked-factory/palette.js ./source/untracked-factory/particle-history.js ./source/untracked-factory/quaternion.js ./source/untracked-factory/state.js ./source/untracked-factory/text-style.js ./source/untracked-factory/vector.js
  • §

    Tween factory

    A tween - inbetween animation - is a small, targeted, time-limited animation where we define the start and end points (key frames) of the animation, and a method for calculating the intermediate values between the two (interpolation, using an easing function).

  • §

    Imports

    import { animationtickers, constructors } from '../core/library.js';
    
    import { convertTime, doCreate, easeEngines, isa_fn, mergeOver, pushUnique, xt, xtGet, xto, λnull, Ωempty } from '../helper/utilities.js';
    
    import { makeTicker } from './ticker.js';
    
    import baseMix from '../mixin/base.js';
    import tweenMix from '../mixin/tween.js';
  • §

    Shared constants

    import { _isArray, _keys, _round, FUNCTION, LINEAR, NAME, PC, T_GROUP, T_TICKER, T_TWEEN, TWEEN, UNDEF, ZERO_STR } from '../helper/shared-vars.js';
  • §

    Set objects - used by tweens as the argument for target.set, target.setArtefact invocations

    • Using an informal cache to cut down on the number of objects created for heavily tweened scenes
    const setObjectsHold = {};
    
    const getSetObjectKey = (defs) => {
    
        if (!defs || !defs.length) return '|';
    
        return '|' + defs.map(d => d.attribute).join('|') + '|';
    };
    
    const getSetObject = (key) => {
    
        if (setObjectsHold[key]) return setObjectsHold[key];
    
        if (key.substring) {
    
            setObjectsHold[key] = {};
            return setObjectsHold[key];
        }
        return {};
    };
  • §

    Tween constructor

    const Tween = function (items = Ωempty) {
    
        this.makeName(items.name);
        this.register();
    
        this.targets = [];
        this.definitions = [];
    
        this.set(this.defs);
    
        const t = items.ticker;
        if (t && !t.substring && t.name && t.type === T_TICKER) items.ticker = t.name;
    
        this.set(items);
    
        this.setObj = getSetObjectKey(this.definitions);
  • §

    status magic numbers: -1 = “before”; 0 = “running”; 1 = “after”.

        this.status = -1;
        this.calculateEffectiveTime();
        this.calculateEffectiveDuration();
  • §

    All Tweens require a Ticker to act as their timeline.

    • If no Ticker attribute is included in the argument object, or the Ticker cannot be located, a new Ticker will be created for this Tween.
        if (animationtickers[items.ticker]) this.addToTicker(items.ticker);
        else {
    
            const tn = `${this.name}_ticker`;
    
            makeTicker({
                name: tn,
                order: this.order,
                subscribers: this.name,
                duration: this.effectiveDuration,
                cycles: xtGet(items.cycles, 1),
                killOnComplete: xtGet(items.killOnComplete, false),
                observer: items.observer,
            });
    
            this.ticker = tn;
    
            animationtickers[tn].recalculateEffectiveDuration();
        }
        return this;
    };
  • §

    Tween prototype

    const P = Tween.prototype = doCreate();
    P.type = T_TWEEN;
    P.lib = TWEEN;
    P.isArtefact = false;
    P.isAsset = false;
  • §

    Mixins

    baseMix(P);
    tweenMix(P);
  • §

    Tween attributes

    const defaultAttributes = {
  • §

    duration - can accept a variety of values:

    • Number, representing milliseconds.
    • String time value, for example '500ms', '0.5s'.
    • % String value - 20% - a relative value measured against the Ticker’s effective duration. For example, if the Ticker has an effective duration of 5000 (5 seconds), and the Tween wants to run for 20% of that time, the Tween’s effective duration will be 1000 (1 second).
        duration: 0,
  • §

    killOnComplete - Boolean flag; if set, the Tween will kill itself when it completes

        killOnComplete: false,
  • §

    The Tween object supports some tween animation hook functions:

    • commenceAction - runs immediately before the Tween’s run function is invoked
    • onRun - triggers immediately after the Tween’s run function is invoked
    • onHalt - triggers immediately after the Tween’s halt function is invoked
    • onResume - triggers immediately after the Tween’s resume function is invoked
    • onReverse - triggers immediately after the Tween’s reverse function is invoked
    • onSeekTo - triggers immediately after the Tween’s seekTo function is invoked
    • onSeekFor - triggers immediately after the Tween’s seekFor function is invoked
    • completeAction - triggers after the Tween completes its run
        commenceAction: null,
        onRun: null,
        onHalt: null,
        onResume: null,
        onReverse: null,
        onSeekTo: null,
        onSeekFor: null,
        completeAction: null,
  • §

    ticker - String name of Ticker object which this Tween will associate itself with.

    • If no Ticker is specified when building a new Tween, it will create a new Ticker object (sharing its name attribute) to associate with.
    • If the attributes below are included in the argument object, they will be passed on to the new Ticker.
    • This attribute is kept in the Tween object, but is excluded from the Tween prototype’s defs object.
  • §

    The following Ticker-related attributes are not stored in the Tween object:

    • cycles - positive integer Number representing the number of cycles the Ticker will undertake before it completes.
    • observer - String name of a RenderAnimation object, or the object itself - halt/resume the ticker based on the running state of the animation object
  • §

    definitions - Array of objects defining the animations to be performed by the Tween. Object attributes include:

    • attribute (required) - String attribute key.
    • start - Number or String value for this attribute’s start point.
    • end - Number or String value for this attribute’s end point.
    • integer - Boolean flag indicating whether we should force results to be integers (default: false)
    • engine - String name for the easing function engine to be used to animate this change; or an easing function supplied by the developer.

    Scrawl-canvas includes functionality to allow start and end values to be defined as Strings, with a measurement suffix (%, px, etc) attached to the number.

    • These values should be of a type that the target object (generally an artefact) expects to receive in its set function.
    • Any object with a set function that takes an object as its argument can be tweened.
    };
    P.defs = mergeOver(P.defs, defaultAttributes);
  • §

    Packet management

    P.packetExclusions = pushUnique(P.packetExclusions, ['definitions', 'targets']);
    P.packetFunctions = pushUnique(P.packetFunctions, ['commenceAction', 'completeAction', 'onRun', 'onHalt', 'onResume', 'onReverse', 'onSeekTo', 'onSeekFor', 'action']);
    
    P.finalizePacketOut = function (copy) {
    
        copy.targets = this.targets.map(t => t.name);
    
        copy.definitions = this.definitions.map(d => {
    
            const res = {};
            res.attribute = d.attribute;
            res.start = d.start;
            res.end = d.end;
    
            if (d.engine && d.engine.substring) res.engine = d.engine;
            else {
    
                if (xt(d.engine) && d.engine != null) {
    
                    const e = this.stringifyFunction(d.engine);
    
                    if (e) {
    
                        res.engine = e;
                        res.engineIsFunction = true;
                    }
                }
            }
            return res;
        });
        return copy;
    };
  • §

    Clone management

    When cloning a tween, we can use an additional attribute in the clone function’s argument object:

    • useNewTicker - Boolean flag - when set, the clone will also create its own Ticker object
    P.postCloneAction = function(clone, items) {
    
        if (items.useNewTicker) {
    
            const ticker = animationtickers[this.ticker];
    
            if (xt(items.cycles)) clone.cycles = items.cycles;
            else if (ticker) clone.cycles = ticker.cycles;
            else clone.cycles = 1;
    
            const cloneTicker = animationtickers[clone.ticker];
            cloneTicker.cycles = clone.cycles;
    
            if (xt(items.duration)) {
    
                clone.duration = items.duration;
                clone.calculateEffectiveDuration();
    
                if (cloneTicker) cloneTicker.recalculateEffectiveDuration();
            }
        }
    
        if (_isArray(clone.definitions)) {
    
            clone.definitions.forEach((def, index) => {
    
                if (def.engineIsFunction) def.engine = this.definitions[index].engine;
            });
        }
    
        return clone;
    };
  • §

    Kill management

    No additional kill functionality required

    • Tweens have the ability to auto-destruct when they complete their run, if their killOnComplete flag has been set to true.
  • §

    Get, Set, deltaSet

    const G = P.getters,
        S = P.setters;
  • §

    definitions

    G.definitions = function() {
    
        return [...this.definitions];
    };
    
    S.definitions = function (item) {
    
        if (item) this.setDefinitions(item);
    };
  • §

    commenceAction

    S.commenceAction = function (item) {
    
        this.commenceAction = item;
    
        if (typeof this.commenceAction != FUNCTION) this.commenceAction = λnull;
    };
  • §

    completeAction

    S.completeAction = function (item) {
    
        this.completeAction = item;
    
        if (typeof this.completeAction != FUNCTION) this.completeAction = λnull;
    };
  • §

    set - we perform some additional functionality in the Tween set function

    • updating the Tween’s Ticker object happens here
    • recalculating effectiveDuration happens here if the time or duration values change
    P.set = function (items = Ωempty) {
    
        const t = items.ticker;
        if (t && !t.substring && t.name && t.type === T_TICKER) items.ticker = t.name;
    
        const setters = this.setters,
            keys = _keys(items),
            d = this.defs,
            ticker = (xt(items.ticker)) ? this.ticker : false;
    
        let key, i, iz, s;
    
        for (i = 0, iz = keys.length; i < iz; i++) {
    
            key = keys[i];
    
            if (key != NAME) {
    
                s = setters[key];
    
                if (s) s.call(this, items[key]);
                else if (typeof d[key] != UNDEF) this[key] = items[key];
            }
        }
    
        if (ticker) {
    
            this.ticker = ticker;
            this.addToTicker(items.ticker);
        }
        else if (xto(items.time, items.duration)) {
    
            this.calculateEffectiveTime();
            this.calculateEffectiveDuration();
        }
        return this;
    };
  • §

    Animation

  • §

    getEndTime - Ticker-related help function

    P.getEndTime = function () {
    
        return this.effectiveTime + this.effectiveDuration;
    };
  • §

    calculateEffectiveDuration

    P.calculateEffectiveDuration = function (item) {
    
        const tweenDuration = xtGet(item, this.duration),
            calculatedDur = convertTime(tweenDuration),
            cDur = calculatedDur[1],
            cType = calculatedDur[0],
            ticker = this.ticker;
    
        let myticker,
            tickerDuration = 0;
    
        this.effectiveDuration = 0;
    
        if (cType === PC) {
    
            if (ticker) {
    
                myticker = animationtickers[ticker];
    
                if (myticker) {
    
                    tickerDuration = myticker.effectiveDuration;
                    this.effectiveDuration = tickerDuration * (cDur / 100);
                }
            }
        }
        else this.effectiveDuration = cDur;
    
        return this;
    };
  • §

    update - main animation function, invoked by the Ticker that this Tween subscribes to; runs once per RequestAnimationFrame while the Ticker is running.

    • items argument is a Ticker ResultObject - Ticker handles all the request and release functionality for this pool object. Treat the items object as read-only.
    • status magic numbers: -1 = “before”; 0 = “running”; 1 = “after”.
    P.update = function (items = Ωempty) {
    
        const tick = items.tick || 0,
            revTick = items.reverseTick || 0,
            effectiveTime = this.effectiveTime,
            effectiveDuration = this.effectiveDuration,
            reversed = this.reversed;
    
        let starts, ends,
            status = 0;
  • §

    Should we do work for this Tween?

        if (!reversed) {
    
            starts = effectiveTime;
            ends = effectiveTime + effectiveDuration;
    
            if (tick < starts) status = -1;
            else if (tick > ends) status = 1;
        }
        else {
    
            starts = effectiveTime + effectiveDuration;
            ends = effectiveTime;
    
            if (revTick > starts) status = 1;
            else if (revTick < ends) status = -1;
        }
  • §

    For Tweens with a duration > 0

        if (effectiveDuration) {
    
            if (!status || status != this.status) {
    
                this.status = status;
                this.doSimpleUpdate(items);
    
                if (!items.next) this.status = (reversed) ? -1 : 1;
            }
        }
  • §

    For Tweens with a duration === 0

        else {
    
            if (status != this.status) {
    
                this.status = status;
                this.doSimpleUpdate(items);
    
                if (!items.next) this.status = (reversed) ? -1 : 1;
            }
        }
    
        if (items.willLoop) {
    
            if (this.reverseOnCycleEnd) this.reversed = !reversed;
            else this.status = -1;
        }
    };
  • §

    doSimpleUpdate - internal function called by update function.

    P.doSimpleUpdate = function (items = Ωempty) {
  • §

    We create handles to a bunch of attributes so that we only need to look them up once each time the function runs

        const starts = this.effectiveTime,
            effectiveDuration = this.effectiveDuration,
            status = this.status,
            definitions = this.definitions,
            targets = this.targets,
            action = this.action,
            setObj = getSetObject(this.setObj);
    
        let progress;
    
        const end = starts + effectiveDuration;
        let elapsed;
    
        if (this.reversed) elapsed = end - items.tick;
        else elapsed = items.tick - starts;
    
        if (effectiveDuration && !status) {
    
            if (elapsed <= 0) progress = 0;
            else if (elapsed >= effectiveDuration) progress = 1;
            else progress = (elapsed / effectiveDuration);
        }
        else progress = (status > 0) ? 1 : 0;
    
        for (let i = 0, iz = definitions.length, val, def; i < iz; i++) {
    
            def = definitions[i];
            val = def.compute(progress);
            setObj[def.attribute] = val;
        }
    
        for (let j = 0, jz = targets.length, t; j < jz; j++) {
    
            t = targets[j];
    
            if (T_GROUP === t.type) t.setArtefacts(setObj);
            else t.set(setObj);
        }
  • §

    We call the action attribute function (if it is defined) at the completion of every update.

        if (action) action();
    };
  • §

    setDefinitions, clearDefinitions

    P.setDefinitions = function (...args) {
    
        this.definitions.length = 0;
        this.definitions.push(...args.flat(Infinity));
        this.setObj = getSetObjectKey(this.definitions);
    
        this.setDefinitionsValues();
    };
    
    P.clearDefinitions = function () {
    
        this.definitions.length = 0;
        this.setObj = getSetObjectKey(this.definitions);
    };
  • §

    setDefinitionsValues - convert start and end values into float Numbers.

    • Scrawl-canvas includes functionality to allow start and end values to be defined as Strings, with a measurement suffix (%, px, etc) attached to the number
    P.setDefinitionsValues = function () {
    
        const parseDefinitionsValue = this.parseDefinitionsValue,
            definitions = this.definitions;
    
        let i, iz, temp, def;
    
        for (i = 0, iz = definitions.length; i < iz; i++) {
    
            def = definitions[i];
    
            if (def) {
    
                temp = parseDefinitionsValue(def.start);
                def.effectiveStart = temp[1];
                def.suffix = temp[0];
    
                temp = parseDefinitionsValue(def.end);
                def.effectiveEnd = temp[1];
    
                def.effectiveChange = def.effectiveEnd - def.effectiveStart;
    
                const start  = def.effectiveStart;
                const change = def.effectiveChange;
                const integer = !!def.integer;
                const suffix  = def.suffix;
    
                let compute, easing;
    
                if (!xt(def.engine)) {
    
                    easing = easeEngines[LINEAR];
                    compute = (p) => {
    
                        let v = start + change * easing(p);
                        if (integer) v = _round(v);
                        return suffix ? (v + suffix) : v;
                    };
  • §

    keep def.engine as string for packets

                    def.engine = LINEAR;
                }
                else if (def.engine.substring) {
    
                    easing = easeEngines[def.engine] || easeEngines[LINEAR];
                    compute = (p) => {
    
                        let v = start + change * easing(p);
                        if (integer) v = _round(v);
                        return suffix ? (v + suffix) : v;
                    };
                }
                else if (isa_fn(def.engine)) {
    
                    easing = def.engine;
                    compute = (p) => {
    
                        let v = easing(start, change, p);
                        if (integer) v = _round(v);
                        return suffix ? (v + suffix) : v;
                    };
                }
                else {
    
                    const easing = easeEngines[LINEAR];
                    compute = (p) => {
    
                        let v = start + change * easing(p);
                        if (integer) v = _round(v);
                        return suffix ? (v + suffix) : v;
                    };
                }
                def.compute = compute;
            }
        }
        return this;
    };
  • §

    parseDefinitionsValue - internal function invoked by setDefinitionsValues function.

    P.parseDefinitionsValue = function (item) {
    
        const result = [ZERO_STR, 0];
    
        if (xt(item)) {
    
            if (item.toFixed) result[1] = item;
            else if (item.substring) {
    
                const m = item.match(/^(-?\d*\.?\d+)(\D*)$/);
    
                if (m) {
    
                    result[1] = parseFloat(m[1]);
                    result[0] = m[2] || ZERO_STR;
                }
            }
        }
        return result;
    };
  • §

    Animation control

  • §

    run

    • Start the Tween from its starting values.
    • Trigger the object’s onRun function.
    P.run = function () {
    
        const t = animationtickers[this.ticker];
    
        if (t) {
    
            this.commenceAction();
            t.run();
    
            if (typeof this.onRun === FUNCTION) this.onRun();
        }
        return this;
    };
  • §

    isRunning - check to see if Tween is in a running state.

    P.isRunning = function () {
    
        const tick = animationtickers[this.ticker];
    
        if (tick) return tick.isRunning();
        return false;
    };
  • §

    halt

    • Stop the Tween at its current point in time
    • Trigger the object’s onHalt function.
    P.halt = function() {
    
        const t = animationtickers[this.ticker];
    
        if (t) {
    
            t.halt();
    
            if (typeof this.onHalt === FUNCTION) this.onHalt();
        }
        return this;
    };
  • §

    reverse

    • Halt the Tween, if it is running.
    • Trigger the object’s onReverse function.
    P.reverse = function() {
    
        const t = animationtickers[this.ticker];
    
        if (t) {
    
            t.reverse();
    
            if (typeof this.onReverse === FUNCTION) this.onReverse();
        }
        return this;
    };
  • §

    resume

    • Start the Tween from its current point in time
    • Trigger the object’s onResume function.
    P.resume = function() {
    
        const t = animationtickers[this.ticker];
    
        if (t) {
    
            t.resume();
    
            if (typeof this.onResume === FUNCTION) this.onResume();
        }
        return this;
    };
  • §

    seekTo

    • Argument - Number representing the millisecond time to move to on the Tween’s Ticker’s timeline
    • Trigger the object’s onSeekTo function.
    P.seekTo = function(milliseconds) {
    
        const t = animationtickers[this.ticker];
    
        if (t) {
    
            t.seekTo(milliseconds);
    
            if (typeof this.onSeekTo === FUNCTION) this.onSeekTo();
        }
        return this;
    };
  • §

    seekFor

    • Argument - Number representing the number of milliseconds to move along the Tween’s Ticker’s timeline (forwards or backwards)
    • Trigger the object’s onSeekFor function.
    P.seekFor = function(milliseconds) {
    
        const t = animationtickers[this.ticker];
    
        if (t) {
    
            t.seekFor(milliseconds);
    
            if (typeof this.onSeekFor === FUNCTION) this.onSeekFor();
        }
        return this;
    };
  • §

    Factory

    scrawl.makeTween({
    
        name: 'my-tween',
    
        duration: 2500,
    
        targets: scrawl.artefact['my-label'],
    
        definitions: [
            {
                attribute: 'textPathPosition',
                start: 1,
                end: 0,
                engine: 'easeIn'
            },
            {
                attribute: 'globalAlpha',
                start: 0,
                end: 1,
                engine: 'easeIn'
            },
            {
                attribute: 'scale',
                start: 0.4,
                end: 1,
                engine: 'easeIn'
            },
        ]
    });
    
    export const makeTween = function (items) {
    
        if (!items) return false;
        return new Tween(items);
    };
    
    constructors.Tween = Tween;