Source: tween/Tween.js

//------------------------------------------------------------------------------
// Constructor scope
//------------------------------------------------------------------------------

/** 
 * Creates a new instance of Tween.
 * 
 * @constructor
 *
 * @param {Object} options Tween object settings.
 *
 * @class
 * @classdesc
 * 
 * The Tween class represents a linear interpolation between a beginning and 
 * ending value.
 */
rune.tween.Tween = function(options) {
    
    //--------------------------------------------------------------------------
    // Default arguments
    //--------------------------------------------------------------------------
    
    /**
     * @ignore
     */
    options = options || {};
    
    //--------------------------------------------------------------------------
    // Private properties
    //--------------------------------------------------------------------------
    
    /**
     * Sets the behavior of a repeating animation.
     *
     * @type {string}
     * @private
     */
    this.m_behavior = options.behavior || rune.tween.Tween.LOOP;
    
    /**
     * The number of times that this animation repeats.
     *
     * @type {number}
     * @private
     */
    this.m_cycles = Math.max(options['cycles'], 1) || 1;
    
    /**
     * The length of the animation, in milliseconds.
     *
     * @type {number}
     * @private
     */
    this.m_duration = options.duration || 2500;
    
    /**
     * The easing behavior for this effect
     *
     * @type {Function}
     * @private
     */
    this.m_easing = options.easing || rune.tween.Sine.easeInOut;
    
    /**
     * The number of times the animation has been repeated.
     *
     * @type {number}
     * @private
     */
    this.m_numCycles = 0;
    
    /**
     * Callback method.
     *
     * @type {Function}
     * @private
     */
    this.m_onDispose = options.onDispose || null;
    
    /**
     * Callback method.
     *
     * @type {Function}
     * @private
     */
    this.m_onInit = options.onInit || null;
    
    /**
     * Callback method.
     *
     * @type {Function}
     * @private
     */
    this.m_onUpdate = options.onUpdate || null;
    
    /**
     * If the animation plays backwards.
     *
     * @type {boolean}
     * @private
     */
    this.m_reversing = false;
    
    /**
     * If the animation is currently playing.
     *
     * @type {boolean}
     * @private
     */
    this.m_running = true;
    
    /**
     * Scope for callback methods.
     *
     * @type {Object}
     * @private
     */
    this.m_scope = options.scope || this;
    
    /**
     * Elapsed time for current cycle.
     *
     * @type {number}
     * @private
     */
    this.m_timeCycle = 0;
    
    /**
     * Object to animate.
     *
     * @type {Object}
     * @private
     */
    this.m_target = options.target || null;
    
    /**
     * Values to animate.
     * 
     * @type {Object}
     * @private
     */
    this.m_values = this.m_createTweenValues(options['args']);
};

//------------------------------------------------------------------------------
// Public static constants
//------------------------------------------------------------------------------

/**
 * Specifies that a repeating animation should progress in a forward direction 
 * on every iteration.
 *
 * @type {string}
 */
rune.tween.Tween.LOOP = "loop";

/**
 * Specifies that a repeating animation should reverse direction on every 
 * iteration. For example, a reversing animation would play forward on the 
 * even iterations and in reverse on the odd iterations.
 *
 * @type {string}
 */
rune.tween.Tween.REVERSE = "reverse";

//------------------------------------------------------------------------------
// Public prototype getter and setter methods
//------------------------------------------------------------------------------

/**
 * If the animation is completed, ie completed the number of requested cycles. 
 * Completed animations are automatically removed by the handler.
 *
 * @member {boolean} complete
 * @memberof rune.tween.Tween
 * @instance
 * @readonly
 */
Object.defineProperty(rune.tween.Tween.prototype, "complete", {
    /**
     * @this rune.tween.Tween
     * @ignore
     */
    get : function() {
        return (this.m_numCycles >= this.m_cycles);
    }
});

/**
 * The progression of the current cycle.
 *
 * @member {boolean} complete
 * @memberof rune.tween.Tween
 * @instance
 * @readonly
 */
Object.defineProperty(rune.tween.Tween.prototype, "progress", {
    /**
     * @this rune.tween.Tween
     * @ignore
     */
    get : function() {
        if (this['complete'] == false) {
            var p = this.m_timeCycle / this.m_duration;
            return Math.round((((this.m_reversing) ? (1 - p) : p)) * 10) / 10;
        }
        
        return 1.0;
    }
});

//------------------------------------------------------------------------------
// Internal prototype methods
//------------------------------------------------------------------------------

/**
 * Called by the manager when the animation is initiated.
 *
 * @returns {undefined}
 * @package
 * @ignore
 */
rune.tween.Tween.prototype.init = function() {
    this.m_exec("m_onInit", false);
};

/**
 * Called by the manager when the animation is updated.
 *
 * @param {number} step Current time step.
 *
 * @returns {undefined}
 * @package
 * @ignore
 */
rune.tween.Tween.prototype.update = function(step) {
    if (this.m_running == true && this['complete'] == false) {
        this.m_updatePlayhead(step);
        this.m_updateValues(step);
        this.m_exec("m_onUpdate", false);
    }
    
    return this['complete'];
};

/**
 * Called by the manager when the animation is completed. The call is made 
 * before the animation is removed from the handler.
 *
 * @returns {undefined}
 * @package
 * @ignore
 */
rune.tween.Tween.prototype.dispose = function() {
    this.m_exec("m_onDispose", true);
    
    this.m_values = null;
    this.m_target = null;
};

//------------------------------------------------------------------------------
// Private prototype methods
//------------------------------------------------------------------------------

/**
 * Calculates the position of playback.
 *
 * @param {number} step Current time step.
 *
 * @returns {undefined}
 * @private
 */
rune.tween.Tween.prototype.m_updatePlayhead = function(step) {
    if (this.m_reversing == true) this.m_timeCycle -= step;
    else this.m_timeCycle += step;
    
    this.m_timeCycle = rune.util.Math.clamp(this.m_timeCycle, 0, this.m_duration);
    
    switch(this.m_behavior) {
        case rune.tween.Tween.REVERSE:
            this.m_updatePlayheadReverse(step);
            break;
        
        default:
            this.m_updatePlayheadLoop(step);
            break;
    }
};

/**
 * Calculates the position of reversed playback.
 * 
 * @param {number} step Current time step.
 *
 * @returns {undefined}
 * @private
 */
rune.tween.Tween.prototype.m_updatePlayheadReverse = function(step) {
    if ((this.m_reversing == false) && (this.m_timeCycle >= this.m_duration)) {
        this.m_timeCycle = this.m_duration;
        this.m_reversing = !this.m_reversing;
        this.m_numCycles++;
    }
    
    if ((this.m_reversing == true) && (this.m_timeCycle <= 0)) {
        this.m_timeCycle = 0;
        this.m_reversing = !this.m_reversing;
        this.m_numCycles++;
    }
};

/**
 * Calculates the looped playback position.
 *
 * @param {number} step Current time step.
 *
 * @returns {undefined}
 * @private
 */
rune.tween.Tween.prototype.m_updatePlayheadLoop = function(step) {
    if (this.m_timeCycle >= this.m_duration) {
        this.m_numCycles++;
        
        if (this['complete']) this.m_timeCycle = this.m_duration;
        else this.m_timeCycle = 0;      
    }
};

/**
 * Updates the properties to be interpolated / animated.
 *
 * @param {number} step Current time step.
 *
 * @returns {undefined}
 * @private
 */
rune.tween.Tween.prototype.m_updateValues = function(step) {
    for (var i = 0; i < this.m_values.length; i++) {
        this.m_target[this.m_values[i]['name']] = this.m_easing(
            this.m_timeCycle,
            this.m_values[i]['start'],
            this.m_values[i]['delta'],
            this.m_duration
        );
    }
};

/**
 * Creates a TweenValue object for each argument to be interpolated.
 *
 * @param {Object} args Objects containing properties to interpolate.
 *
 * @returns {Array.<rune.tween.TweenValue>}
 * @private
 */
rune.tween.Tween.prototype.m_createTweenValues = function(args) {
    var values = [];
    for (var arg in args) {
        if (arg in this.m_target) {
            var value = new rune.tween.TweenValue(
                arg,
                this.m_target[arg],
                args[arg]
            );
            
            values.push(value);
        }
    }
    
    return values;
};

/**
 * Execute requested callback method.
 *
 * @param {string} name Name of callback.
 * @param {boolean} complete If the animation must be completed before the callback method can be called.
 *
 * @returns {undefined}
 * @private
 */
rune.tween.Tween.prototype.m_exec = function(name, complete) {
    if (this['complete'] == complete) {
        if (typeof this[name] === "function") {
            this[name].call(
                this.m_scope, 
                this.m_target, 
                this
            );
        }
    }
};