- Jump To … +
  ./source/core/animation-loop.js ./source/core/display-cycle.js ./source/core/document-root-elements.js ./source/core/document.js ./source/core/events.js ./source/core/init.js ./source/core/library.js ./source/core/random-seed.js ./source/core/shared-vars.js ./source/core/snippets.js ./source/core/system-flags.js ./source/core/user-interaction.js ./source/core/utilities.js ./source/factory/action.js ./source/factory/anchor.js ./source/factory/animation.js ./source/factory/array-pool.js ./source/factory/bezier.js ./source/factory/block.js ./source/factory/canvas.js ./source/factory/cell-fragment.js ./source/factory/cell.js ./source/factory/cog.js ./source/factory/color.js ./source/factory/conic-gradient.js ./source/factory/coordinate.js ./source/factory/crescent.js ./source/factory/drag-zone.js ./source/factory/element.js ./source/factory/emitter.js ./source/factory/filter-engine-bluenoise-data.js ./source/factory/filter-engine.js ./source/factory/filter.js ./source/factory/font-attributes.js ./source/factory/gradient.js ./source/factory/grid.js ./source/factory/group.js ./source/factory/image-asset.js ./source/factory/keyboard-zone.js ./source/factory/line-spiral.js ./source/factory/line.js ./source/factory/loom.js ./source/factory/mesh.js ./source/factory/net.js ./source/factory/noise-asset.js ./source/factory/observe-update.js ./source/factory/oval.js ./source/factory/palette.js ./source/factory/particle-force.js ./source/factory/particle-history.js ./source/factory/particle-spring.js ./source/factory/particle-world.js ./source/factory/particle.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/radial-gradient.js ./source/factory/raw-asset.js ./source/factory/reaction-diffusion-asset.js ./source/factory/rectangle.js ./source/factory/render-animation.js ./source/factory/shape.js ./source/factory/spiral.js ./source/factory/sprite-asset.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/unstacked-element.js ./source/factory/vector.js ./source/factory/video-asset.js ./source/factory/wheel.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/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/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/shape-path-calculation.js ./source/mixin/styles.js ./source/mixin/tween.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).

  Scrawl-canvas includes a full range of functionality to implement tweening. Any Scrawl-canvas object that includes a `set` function, which accepts a `{key:value}` object as its single argument, can be tweened.

  - Tween animations are defined by creating a Tween object using the `makeTween` factory function.
  - Each Tween object includes a `targets` Array - one Tween may animate many objects.
  - Each Tween also includes a `definitions` Array which sets out the details of the animation to be performed.
  - The `definitions` Array can include multiple objects, each defining a change to a specific attribute that needs to be applied to the target objects.
  - Any number-type attribute can be animated using a Tween - this includes integer and float Numbers, percentage Strings (`12.5%`) and measurement Strings (`20px`).
  - We can run multiple Tween animations at the same time; Tweens can overlap (start one tween while another is running).
  - We can dynamically halt, resume, restart and terminate Tweens in response to user interactions.
  - Tweens include `packet` functionality: they can be saved, and cloned.

  Scrawl-canvas separates the timing aspects of its Tweens from their definitions.

  - Tweens run against a timeline object called a Ticker.
  - When we define a Tween, we can either assign it to an existing Ticker, or get the Tween factory to create a new Ticker for it.
  - Tickers can also have Action objects assigned to them, allowing us to trigger functions at specific points along the timeline.
- §

  ##### Using other tween factories

  We don’t recommend using other tween factories - such as Greensock - with Scrawl-canvas objects at this time.

  - The reason is simple. All updates to Scrawl-canvas objects should be made through their dedicated `set`, `setDelta` and related functions, which take a single Javascript object as their argument.
  - When attributes are updated in this way, Scrawl-canvas makes sure various `dirty` flags get set (among other work), so the object can update itself appropriately at the most convenient time for itself.
  - Libraries like GSAP works by directly updating object attributes, which misses out the necessary work of setting the appropriate `dirty` flags - thus the object will not know that it needs to do work.
  - This does not mean you can’t use other tween libraries in your web page - just be aware of the limitations of trying to use them with Scrawl-canvas objects!
- §

  TODO: basic packet and kill functionality tested in Demo DOM-004, but there’s a lot of Ticker/Tween/Action functionality that needs to be explored and tested further (see Ticker TODO section for issues and suggested work).

- §

  #### Demos:

  - Canvas-005 - Cell-locked, and Entity-locked, gradients; animating gradients by delta, and by tween
  - Canvas-006 - Canvas tween stress test
  - DOM-004 - Limitless rockets (clone and destroy elements, tweens, tickers)
  - DOM-005 - DOM tween stress test
  - DOM-006 - Tween actions on a DOM element; tracking tween and ticker activity (analytics)
  - Snippets-001 - Scrawl-canvas DOM element snippets
- §

  #### Imports

      import { animationtickers, constructors } from '../core/library.js';

      import { convertTime, doCreate, easeEngines, mergeOver, pushUnique, xt, xtGet, xto, λnull, Ωempty } from '../core/utilities.js';

      import { makeTicker } from './ticker.js';

      import baseMix from '../mixin/base.js';
      import tweenMix from '../mixin/tween.js';

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

- §

  #### Tween constructor

      const Tween = function (items = Ωempty) {

          let tn;

          this.makeName(items.name);
          this.register();

          this.set(this.defs);
          this.set(items);

          this.setDefinitionsValues();

- §

  `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 {

              tn = `${this.name}_ticker`;

              makeTicker({
                  name: tn,
                  order: this.order,
                  subscribers: this.name,
                  duration: this.effectiveDuration,
                  eventChoke: xtGet(items.eventChoke, 0),
                  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

  - Attributes defined in the base mixin: name.
  - Attributes defined in the tween mixin: order, ticker, targets, time, action, reverseOnCycleEnd, reversed.

      const defaultAttributes = {

- §

  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.

          definitions: null,

- §

  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.
  - eventChoke Number representing minimum number of milliseconds between Ticker event emissions
  - observer - String name of a RenderAnimation object, or the object itself - halt/resume the ticker based on the running state of the animation object

      };
      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) {

          if (_isArray(this.targets)) copy.targets = this.targets.map(t => t.name);

          if (_isArray(this.definitions)) {

              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.substring;
                  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 ticker, 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 [].concat(this.definitions);
      };

      S.definitions = function (item) {

          this.definitions = [].concat(item);
          this.setDefinitionsValues();
      };

- §

  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 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,
              actions = this.engineActions,
              effectiveDuration = this.effectiveDuration,
              status = this.status,
              definitions = this.definitions,
              targets = this.targets,
              action = this.action,

- §

  We store the `setObj` object as an attribute on the Tween object for convenience, and to cut down on the number of objects created during the lifetime of the Tween.

              setObj = this.setObj || {};

          let def, engine, val, effectiveStart, effectiveChange, int, suffix, attribute,
              i, iz, j, jz, progress;

          const effectiveTick = (this.reversed) ? items.reverseTick - starts : items.tick - starts;

          if (effectiveDuration && !status) progress = effectiveTick / effectiveDuration;
          else progress = (status > 0) ? 1 : 0;

          for (i = 0, iz = definitions.length; i < iz; i++) {

              def = definitions[i];
              engine = def.engine;
              effectiveStart = def.effectiveStart;
              effectiveChange = def.effectiveChange;
              int = def.integer;
              suffix = def.suffix;
              attribute = def.attribute;

- §

  Invoke the appropriate easing function for this particular definition object

              if (engine.substring) val = actions(engine, effectiveStart, effectiveChange, progress);
              else val = engine(effectiveStart, effectiveChange, progress);

              if (int) val = _round(val);

              if (suffix) val += suffix;

              setObj[attribute] = val;
          }

          for (j = 0, jz = targets.length; j < jz; j++) {

              const t = targets[j];

              if (T_GROUP == t.type) t.setArtefacts(setObj);
              else t.set(setObj);
          }

          this.setObj = setObj;

- §

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

          if (action) action();
      };

- §

  `engineActions` - internal function to run built-in easing engines

  - engine - String name of the required easing. All built-in easing engines are defined in the `core/utilities` file
  - start - the `effectiveStart` value, in milliseconds.
  - change - the time in milliseconds that this animation will take (`effectiveEnd - effectiveStart`).
  - position - the time elapsed since this Tween started running

  Scrawl-canvas easing engines use the following metaphor to define their names:

  - `out` indicates an acceleration. Think of a train leaving the station.
  - `in` indicates a deceleration. Think of a train arriving the station.
  - For the legacy engines, igher numbers indicates a more intense change between starting, middle and ending speeds

      P.engineActions = function(engine, start, change, position) {

          const e = (null != easeEngines[engine]) ? engine : LINEAR;
          return start + (change * easeEngines[e](position));
      };

- §

  `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];

- §

  The default easing function is `linear`

                  if (!xt(def.engine)) def.engine = LINEAR;

                  def.effectiveChange = def.effectiveEnd - def.effectiveStart;
              }
          }
          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 a = item.match(/^-?\d+\.?\d*(\D*)/);

                  if (xt(a[0])) result[1] = parseFloat(a);
                  if (xt(a[1])) result[0] = a[1];
              }
          }
          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-phrase'],

          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;