Rekapi src/rekapi.actor.js

Actor

method
Kapi.Actor()
  • @param: {Object}opt_config
  • @constructor:

Description

Create a Kapi.Actor instance. Note that the rest of the API docs for Kapi.Actor will simply refer to this Object as Actor.

Valid properties of opt_config (you can omit the ones you don't need):

  • context (Object): The context that this Actor is associated with. If omitted, this Actor gets the Kapi instance's context when it is added with Kapi#addActor.
  • setup (Function): A function that gets called when the Actor is added with Kapi#addActor.
  • update (Function(Object, Object)): A function that gets called every time that the Actor's state is updated. It receives two parameters: A reference to the Actor's context and an Object containing the current state properties.
  • teardown (Function): A function that gets called when the Actor is removed with Kapi#removeActor.

Kapi.Actor does not render to any context. It is a base class. Use the Kapi.CanvasActor or Kapi.DOMActor subclasses to render to the screen. You can also make your own rendering subclass - see the source code for the aforementioned examples.

Example

Source

Kapi.Actor = function (opt_config) {

    opt_config = opt_config || {};

    // Steal the `Tweenable` constructor.
    Tweenable.call(this);

    _.extend(this, {
      '_propertyTracks': {}
      ,'_timelinePropertyCaches': {}
      ,'_timelinePropertyCacheIndex': []
      ,'_keyframeProperties': {}
      ,'id': _.uniqueId()
      ,'setup': opt_config.setup || noop
      ,'update': opt_config.update || noop
      ,'teardown': opt_config.teardown || noop
      ,'data': {}
    });

    if (opt_config.context) {
      this.context(opt_config.context);
    }

    return this;
  };
  var Actor = Kapi.Actor;


  // Kind of a fun way to set up an inheritance chain.  `ActorMethods` prevents
  // methods on `Actor.prototype` from polluting `Tweenable`'s prototype with
  // `Actor` specific methods.
  var ActorMethods = function () {};
  ActorMethods.prototype = Tweenable.prototype;
  Actor.prototype = new ActorMethods();
  // But the magic doesn't stop here!  `Actor`'s constructor steals the
  // `Tweenable` constructor.

context

method
Actor.prototype.context()
  • @param: {Object}opt_context
  • @return: {Object}

Description

Get and optionally set the Actor's context.

Example

Source

Actor.prototype.context = function (opt_context) {
    if (opt_context) {
      this._context = opt_context;
    }

    return this._context;
  };

keyframe

method
Actor.prototype.keyframe()
  • @param: {number}millisecondWhere on the timeline to set the keyframe.
  • @param: {Object}propertiesKeyframe properties to set for the keyframe.
  • @param: {string,Object}opt_easingOptional easing string or configuration object.
  • @return: {Kapi.Actor}

Description

Create a keyframe for the Actor. millisecond defines where in the animation to place the keyframe, in milliseconds (assumes that 0 is when the animation began). The animation length will automatically "grow" to accommodate any keyframe position.

properties should contain all of the properties that define the keyframe's state. These properties can be any value that can be tweened by Shifty (numbers, color strings, CSS properties).

Note: Internally, this creates Kapi.KeyframePropertys and places them on a "track." These Kapi.KeyframePropertys are managed for you by the Actor APIs.

Easing

opt_easing, if specified, can be a string or an Object. If it's a string, all properties in properties will have the same easing formula applied to them. For example:

actor.keyframe(1000, {
    'x': 100,
    'y': 100
  }, 'easeOutSine');

Both x and y will have easeOutSine applied to them. You can also specify multiple easing formulas with an Object:

actor.keyframe(1000, {
    'x': 100,
    'y': 100
  }, {
    'x': 'easeinSine',
    'y': 'easeOutSine'
  });

x will ease with easeInSine, and y will ease with easeOutSine. Any unspecified properties will ease with linear. If opt_easing is omitted, all properties will default to linear.

Keyframe inheritance

Keyframes always inherit missing properties from the keyframes that came before them. For example:

actor.keyframe(0, {
  'x': 100
}).keyframe(1000{
  // Inheriting the `x` from above!
  'y': 50
});

Keyframe 1000 will have a y of 50, and an x of 100, because x was inherited from keyframe 0.

Source

Actor.prototype.keyframe = function keyframe (
      millisecond, properties, opt_easing) {

    var originalEasingString;

    // TODO:  The opt_easing logic seems way overcomplicated, it's probably out
    // of date.  Multiple eases landed first in Rekapi, then were pushed
    // upstream into Shifty.  There's likely some redundant logic here.
    opt_easing = opt_easing || DEFAULT_EASING;

    if (typeof opt_easing === 'string') {
      originalEasingString = opt_easing;
      opt_easing = {};
      _.each(properties, function (property, propertyName) {
        opt_easing[propertyName] = originalEasingString;
      });
    }

    // If `opt_easing` was passed as an Object, this will fill in any missing
    // opt_easing properties with the default equation.
    _.each(properties, function (property, propertyName) {
      opt_easing[propertyName] = opt_easing[propertyName] || DEFAULT_EASING;
    });

    _.each(properties, function (value, name) {
      var newKeyframeProperty = new Kapi.KeyframeProperty(
          this, millisecond, name, value, opt_easing[name]);

      this._keyframeProperties[newKeyframeProperty.id] = newKeyframeProperty;

      if (!this._propertyTracks[name]) {
        this._propertyTracks[name] = [];
      }

      this._propertyTracks[name].push(newKeyframeProperty);
      sortPropertyTracks(this);
    }, this);

    if (this.kapi) {
      recalculateAnimationLength(this.kapi, _);
    }

    invalidatePropertyCache(this);

    return this;
  };

getKeyframeProperty

method
Actor.prototype.getKeyframeProperty()
  • @param: {string}propertyThe name of the property.
  • @param: {number}indexThe 0-based index of the KeyframeProperty in the Actor's KeyframeProperty track.
  • @return: {Kapi.KeyframeProperty,undefined}

Description

Gets the Kapi.KeyframeProperty from an Actor's Kapi.KeyframeProperty track. Returns undefined if there were no properties found with the specified parameters.

Example

Source

Actor.prototype.getKeyframeProperty = function (property, index) {
    if (this._propertyTracks[property]
        && this._propertyTracks[property][index]) {
      return this._propertyTracks[property][index];
    }
  };

modifyKeyframeProperty

method
Actor.prototype.modifyKeyframeProperty()
  • @param: {string}propertyThe name of the property to modify
  • @param: {number}indexThe property track index of the KeyframeProperty to modify
  • @param: {Object}newPropertiesThe properties to augment the KeyframeProperty with
  • @return: {Kapi.Actor}

Description

Modify a specified Kapi.KeyframeProperty stored on an Actor. Essentially, this calls KeyframeProperty#modifyWith (passing along newProperties) and then performs some cleanup.

Example

Source

Actor.prototype.modifyKeyframeProperty = function (
      property, index, newProperties) {

    if (this._propertyTracks[property]
        && this._propertyTracks[property][index]) {
      this._propertyTracks[property][index].modifyWith(newProperties);
    }

    cleanupAfterKeyframeModification(this);

    return this;
  };

getTrackNames

method
Actor.prototype.getTrackNames()
  • @return: {Array.<string>}

Description

Get a list of all the track names for an Actor.

Example

Source

Actor.prototype.getTrackNames = function () {
    return _.keys(this._propertyTracks);
  };

getTrackLength

method
Actor.prototype.getTrackLength()
  • @param: {string}trackName
  • @return: {number}

Description

Get the property track length for an Actor (how many KeyframePropertys are in a given property track).

Example

Source

Actor.prototype.getTrackLength = function (trackName) {
    if (!this._propertyTracks[trackName]) {
      return;
    }

    return this._propertyTracks[trackName].length;
  };

copyProperties

method
Actor.prototype.copyProperties()
  • @param: {number}copyToThe millisecond to copy KeyframeProperties to
  • @param: {number}copyFromThe millisecond to copy KeyframeProperties from
  • @return: {Kapi.Actor}

Description

Copy all of the properties that at one point in the timeline to another point. This is useful for many things, particularly for bringing a Kapi.Actor back to its original position.

Example

Source

Actor.prototype.copyProperties = function (copyTo, copyFrom) {
    var sourcePositions = {};
    var sourceEasings = {};

    _.each(this._propertyTracks, function (propertyTrack, trackName) {
      var foundProperty = findPropertyAtMillisecondInTrack(this, trackName,
          copyFrom);

      if (foundProperty) {
        sourcePositions[trackName] = foundProperty.value;
        sourceEasings[trackName] = foundProperty.easing;
      }
    }, this);

    this.keyframe(copyTo, sourcePositions, sourceEasings);
    return this;
  };

wait

method
Actor.prototype.wait()
  • @param: {number}untilAt what point in the animation the Actor should wait until (relative to the start of the animation)
  • @return: {Kapi.Actor}

Description

Extend the last state on this Actor's timeline to create a animation wait. The state does not change during this time.

Example

Source

Actor.prototype.wait = function (until) {
    var length = this.getEnd();

    if (until <= length) {
      return this;
    }

    var end = this.getEnd();
    var latestProps = getLatestPropeties(this, this.getEnd());
    var serializedProps = {};
    var serializedEasings = {};

    _.each(latestProps, function (latestProp, propName) {
      serializedProps[propName] = latestProp.value;
      serializedEasings[propName] = latestProp.easing;
    });

    this.removeKeyframe(end);
    this.keyframe(end, serializedProps, serializedEasings);
    this.keyframe(until, serializedProps, serializedEasings);

    return this;
  };

getStart

method
Actor.prototype.getStart()
  • @param: {string}opt_trackName
  • @return: {number}

Description

Get the millisecond of the first state of an Actor (when it first starts animating). You can get the start time of a specific track with opt_trackName.

Example

Source

Actor.prototype.getStart = function (opt_trackName) {
    var starts = [];

    if (opt_trackName) {
      starts.push(this._propertyTracks[opt_trackName][0].millisecond);
    } else {
      _.each(this._propertyTracks, function (propertyTrack) {
        if (propertyTrack.length) {
          starts.push(propertyTrack[0].millisecond);
        }
      });
    }

    if (starts.length === 0) {
      starts = [0];
    }

    return Math.min.apply(Math, starts);
  };

getEnd

method
Actor.prototype.getEnd()
  • @param: {string}opt_trackName
  • @return: {number}

Description

Get the millisecond of the last state of an Actor (when it is done animating). You can get the last state for a specific track with opt_trackName.

Example

Source

Actor.prototype.getEnd = function (opt_trackName) {
    var latest = 0;
    var tracksToInspect = this._propertyTracks;

    if (opt_trackName) {
      tracksToInspect = {};
      tracksToInspect[opt_trackName] = this._propertyTracks[opt_trackName];
    }

    _.each(tracksToInspect, function (propertyTrack) {
      if (propertyTrack.length) {
        var trackLength = _.last(propertyTrack).millisecond;

        if (trackLength > latest) {
          latest = trackLength;
        }
      }
    }, this);

    return latest;
  };

getLength

method
Actor.prototype.getLength()
  • @param: {string}opt_trackName
  • @return: {number}

Description

Get the length of time in milliseconds that an Actor animates for. You can get the length of time that a specific track animates for with opt_trackName.

Example

Source

Actor.prototype.getLength = function (opt_trackName) {
    return this.getEnd(opt_trackName) - this.getStart(opt_trackName);
  };

hasKeyframeAt

method
Actor.prototype.hasKeyframeAt()
  • @param: {number}millisecondPoint on the timeline to query.
  • @param: {string}opt_trackNameOptional name of a property track.
  • @return: {boolean}

Description

Determines if an actor has a keyframe set at a given millisecond. Can optionally scope the lookup to a specific property name.

Source

Actor.prototype.hasKeyframeAt = function(millisecond, opt_trackName) {
    var tracks = this._propertyTracks;

    if (opt_trackName) {
      if (!_.has(tracks, opt_trackName)) {
        return false;
      }
      tracks = _.pick(tracks, opt_trackName);
    }

    return _.find(tracks, function (propertyTrack, trackName) {
      var retrievedProperty =
          findPropertyAtMillisecondInTrack(this, trackName, millisecond);
      return retrievedProperty !== undefined;
    }, this) !== undefined;
  };

moveKeyframe

method
Actor.prototype.moveKeyframe()
  • @param: {number}fromThe millisecond of the keyframe to be moved.
  • @param: {number}toThe millisecond of where the keyframe should be moved to.
  • @return: {boolean}Whether or not the keyframe was successfully moved.

Description

Moves a Keyframe from one point on the timeline to another. Although this method does error checking for you to make sure the operation can be safely performed, an effective pattern is to use hasKeyframeAt to see if there is already a keyframe at the requested to destination.

Example

Source

Actor.prototype.moveKeyframe = function (from, to) {
    if (!this.hasKeyframeAt(from) || this.hasKeyframeAt(to)) {
      return false;
    }

    _.each(this._propertyTracks, function (propertyTrack, trackName) {
      var property = findPropertyAtMillisecondInTrack(this, trackName, from);

      if (property) {
        property.modifyWith({
          'millisecond': to
        });
      }
    }, this);

    cleanupAfterKeyframeModification(this);

    return true;
  };

modifyKeyframe

method
Actor.prototype.modifyKeyframe()
  • @param: {number}millisecond
  • @param: {Object}stateModification
  • @param: {Object}opt_easingModification
  • @return: {Kapi.Actor}

Description

Augment the value or easing of the Kapi.KeyframePropertys at a given millisecond. Any Kapi.KeyframePropertys omitted in stateModification or opt_easing are not modified. Here's how you might use it:

actor.keyframe(0, {
  'x': 10,
  'y': 20
}).keyframe(1000, {
  'x': 20,
  'y': 40
}).keyframe(2000, {
  'x': 30,
  'y': 60
})

// Changes the state of the keyframe at millisecond 1000.
// Modifies the value of 'y' and the easing of 'x.'
actor.modifyKeyframe(1000, {
  'y': 150
}, {
  'x': 'easeFrom'
});

Example

Source

Actor.prototype.modifyKeyframe = function (
      millisecond, stateModification, opt_easingModification) {
    opt_easingModification = opt_easingModification || {};

    _.each(this._propertyTracks, function (propertyTrack, trackName) {
      var property = findPropertyAtMillisecondInTrack(
          this, trackName, millisecond);

      if (property) {
        property.modifyWith({
          'value': stateModification[trackName]
          ,'easing': opt_easingModification[trackName]
        });
      }
    }, this);

    return this;
  };

removeKeyframe

method
Actor.prototype.removeKeyframe()
  • @param: {number}millisecondThe location on the timeline of the keyframe to remove.
  • @return: {Kapi.Actor}

Description

Remove all Kapi.KeyframePropertys at a given millisecond in the animation.

Example

Source

Actor.prototype.removeKeyframe = function (millisecond) {
    _.each(this._propertyTracks, function (propertyTrack, propertyName) {
      var i = -1;
      var foundProperty = false;

      _.find(propertyTrack, function (keyframeProperty) {
        i++;
        foundProperty = (millisecond === keyframeProperty.millisecond);
        return foundProperty;
      });

      if (foundProperty) {
        var removedProperty = propertyTrack.splice(i, 1)[0];

        if (removedProperty) {
          delete this._keyframeProperties[removedProperty.id];
        }
      }
    }, this);

    if (this.kapi) {
      recalculateAnimationLength(this.kapi, _);
    }

    invalidatePropertyCache(this);

    return this;
  };

removeAllKeyframeProperties

method
Actor.prototype.removeAllKeyframeProperties()
  • @return: {Kapi.Actor}

Description

Remove all KeyframePropertys set on the Actor.

Example

Source

Actor.prototype.removeAllKeyframeProperties = function () {
    _.each(this._propertyTracks, function (propertyTrack, propertyName) {
      propertyTrack.length = 0;
    }, this);

    this._keyframeProperties = {};
    return this.removeKeyframe(0);
  };

updateState

method
Actor.prototype.updateState()
  • @param: {number}millisecond
  • @return: {Kapi.Actor}

Description

Calculate and set the Actor's position at millisecond in the animation.

Example

Source

Actor.prototype.updateState = function (millisecond) {
    var startMs = this.getStart();
    var endMs = this.getEnd();

    millisecond = Math.min(endMs, millisecond);

    if (startMs <= millisecond) {
      var latestCacheId = getPropertyCacheIdForMillisecond(this, millisecond);
      var propertiesToInterpolate =
          this._timelinePropertyCaches[this._timelinePropertyCacheIndex[
          latestCacheId]];
      var interpolatedObject = {};

      _.each(propertiesToInterpolate, function (keyframeProperty, propName) {
        // TODO: Try to get rid of this null check
        if (keyframeProperty) {
          if (this._beforeKeyframePropertyInterpolate !== noop) {
            this._beforeKeyframePropertyInterpolate(keyframeProperty);
          }

          interpolatedObject[propName] =
              keyframeProperty.getValueAt(millisecond);

          if (this._afterKeyframePropertyInterpolate !== noop) {
            this._afterKeyframePropertyInterpolate(
                keyframeProperty, interpolatedObject);
          }
        }
      }, this);

      this.set(interpolatedObject);
    }

    return this;
  };

exportTimeline

method
Actor.prototype.exportTimeline()
  • @return: {Object}

Description

Export a serializable Object of this Actor's timeline property tracks and Kapi.KeyframePropertys.

Example

Source

Actor.prototype.exportTimeline = function () {
    var exportData = {
      'start': this.getStart()
      ,'end': this.getEnd()
      ,'trackNames': this.getTrackNames()
      ,'propertyTracks': {}
    };

    _.each(this._propertyTracks, function (propertyTrack, trackName) {
      var trackAlias = exportData.propertyTracks[trackName] = [];
      _.each(propertyTrack, function (keyframeProperty) {
        trackAlias.push(keyframeProperty.exportPropertyData());
      });
    });

    return exportData;
  };

importTimeline

method
Actor.prototype.importTimeline()
  • @param: {Object}actorDataAny object that has the same data format as the object generated from Actor#exportTimeline.

Description

Import an Object to augment this actor's state. Does not remove keyframe properties before importing new ones, so this could be used to "merge" keyframes across multiple actors.

Source

Actor.prototype.importTimeline = function (actorData) {
    _.each(actorData.propertyTracks, function (propertyTrack) {
      _.each(propertyTrack, function (property) {
        var obj = {};
        obj[property.name] = property.value;
        this.keyframe(property.millisecond, obj, property.easing);
      }, this);
    }, this);
  };

});