Source: animate/Timeline.js

animate/Timeline.js

import Tween from './Tween';

/**
 * The Timeline class represents a
 * @memberof PIXI.animate
 * @class Timeline
 * @param {PIXI.DisplayObject} Target The target for this string of tweens.
 * @extends Array
 * @constructor
 */
const Timeline = function(target) {
    Array.call(this);

    /**
     * The target DisplayObject.
     * @name PIXI.animate.Timeline#target
     * @type {PIXI.DisplayObject}
     */
    this.target = target;

    /**
     * Current properties in the tween, to make building the timeline more
     * efficient.
     * @name PIXI.animate.Timeline#_currentProps
     * @type {Object}
     * @private
     */
    this._currentProps = {};
};

const p = Timeline.prototype = Object.create(Array.prototype);

/**
 * Adds one or more tweens (or timelines) to this timeline. The tweens will be paused (to remove them from the normal ticking system)
 * and managed by this timeline. Adding a tween to multiple timelines will result in unexpected behaviour.
 * @method PIXI.animate.Timeline#addTween
 * @param tween The tween(s) to add. Accepts multiple arguments.
 * @return Tween The first tween that was passed in.
 */
p.addTween = function(properties, startFrame, duration, ease) {
    this.extendLastFrame(startFrame - 1);
    //ownership of startProps is passed to the new Tween - this object should not be reused
    let startProps = {};
    let prop;
    //figure out what the starting values for this tween should be
    for (prop in properties) {
        //if we have already set that property in an earlier tween, use the ending value
        if (this._currentProps.hasOwnProperty(prop)) {
            startProps[prop] = this._currentProps[prop];
        }
        //otherwise, get the current value
        else {
            let startValue = startProps[prop] = this.getPropFromShorthand(prop);
            //go through previous tweens to set the value so that when the timeline loops
            //around, the values are set properly - having each tween know what came before
            //allows us to set to a specific frame without running through the entire timeline
            for (let i = this.length - 1; i >= 0; --i) {
                this[i].startProps[prop] = startValue;
                this[i].endProps[prop] = startValue;
            }
        }
    }
    //create the new Tween and add it to the list
    let tween = new Tween(this.target, startProps, properties, startFrame, duration, ease);
    this.push(tween);
    //update starting values for the next tween - if tweened values included 'p', then Tween
    //parsed that to add additional data that is required
    Object.assign(this._currentProps, tween.endProps);
};

/**
 * Add a single keyframe that doesn't tween.
 * @method PIXI.animate.Timeline#addKeyframe
 * @param {Object} properties The properties to set.
 * @param {int} startFrame The starting frame index.
 */
p.addKeyframe = function(properties, startFrame) {
    this.extendLastFrame(startFrame - 1);
    let startProps = Object.assign({}, this._currentProps, properties);
    //create the new Tween and add it to the list
    let tween = new Tween(this.target, startProps, null, startFrame, 0);
    this.push(tween);
    Object.assign(this._currentProps, tween.endProps);
};

/**
 * Extend the last frame of the tween.
 * @method PIXI.animate.Timeline#extendLastFrame
 * @param {int} endFrame The ending frame index.
 */
p.extendLastFrame = function(endFrame) {
    if (this.length) {
        let prevTween = this[this.length - 1];
        if (prevTween.endFrame < endFrame) {
            if (prevTween.isTweenlessFrame) {
                prevTween.endFrame = endFrame;
            } else {
                this.addKeyframe(
                    this._currentProps,
                    prevTween.endFrame + 1,
                    endFrame - prevTween.endFrame + 1
                );
            }
        }
    }
};

/**
 * Get the value for a property
 * @method PIXI.animate.Timeline#getPropFromShorthand
 * @param {string} prop
 */
p.getPropFromShorthand = function(prop) {
    const target = this.target;
    switch (prop) {
        case 'x':
            return target.position.x;
        case 'y':
            return target.position.y;
        case 'sx':
            return target.scale.x;
        case 'sy':
            return target.scale.y;
        case 'kx':
            return target.skew.x;
        case 'ky':
            return target.skew.y;
        case 'r':
            return target.rotation;
        case 'a':
            return target.alpha;
        case 'v':
            return target.visible;
        case 'm':
            return target.mask;
            // case 't':
            //   return target.tint;
            //not sure if we'll actually handle graphics this way?
            //g: return null;
    }
    return null;
};

// Assign to namespace
export default Timeline;