API Docs for: 0.2
Show:

File: src/animation.js

"use strict";


var paper = require("./getPaper");
var Tween = require("./tween");
var frameManager = require("./frameManager");
var easing = require("./easing");


/**
 *  Animation class. Default settings are :
 *
 *  ````
 *      var defaults = {
 *           duration: 400,
 *           easing: "linear",
 *           complete: undefined,
 *           step: undefined
 *      };
 *  ````
 *  @class Animation
 *  @constructor
 *  @param {Object} item a paper.js Item instance, which will be animated.
 *  @param {Object} properties properties to animate
 *  @param {Object} settings
 *  @param {Number} settings.duration Duration of the animation, in ms
 *  @param {String} settings.easing
 *  @param {Function} settings.complete Called when the animation is over, in `.end()`. The item is passed as this, the animation as 1st argument
 *  @param {Function} settings.step Called on each `.tick()`
 *  @param {Mixed} settings.repeat function or true or an integer. The animation will repeat as long as function returns `true`, `true` or `repeat` > 0, decrementing by 1 each time.
 */
function Animation(item, properties, settings, _continue) {
        var self = this;

        /**
         *  True if the animation is stopped
         *  @property {Bool} stopped
         */
        self.stopped = false;
        /**
         *  Time when the Animation is created
         *  @property {Timestamp} startTime
         *  @readonly
         */
        self.startTime = new Date().getTime();
        /**
         *  Settings, after being normalized in {{#crossLink "_initializeSettings"}}{{/crossLink}}
         *  @property {Object} settings
         */
        self.settings = _initializeSettings(settings);
        /**
         *  The animated `paper.Item`
         *  @property {Object} item
         *  @readonly
         */
        self.item = item;
        /**
         *  If provided, use parentItem to use .data and .onFrame. If not, use self.item;
         *  @property {Object} itemForAnimations
         *  @readonly
         */
        self.itemForAnimations = self.settings.parentItem || self.item;

        /**
         * Repeat parameter.
         * If Function, the animation is repeated as long as the function returns `true`.
         * If `true`, the animation is repeated until `.end(true)` is called.
         * If `repeat` is an integer, the animation is repeated until `repeat` is <= 0.
         * Default `0`  
         * @property {Mixed} repeat
         */
        self.repeat = self.settings.repeat || 0;
        if (typeof self.settings.repeat === "function") {
            var _repeatCallback = self.settings.repeat;
            self.repeatCallback = function() {
                if (!!_repeatCallback(item, self)) {
                    return new Animation(item, properties, settings, _continue);
                }
                return null;
            };
        } else {
            if (self.repeat === true || self.repeat > 0) {
                self.repeatCallback = function(newRepeat) {
                    settings.repeat = newRepeat;
                    // used for the repeat feature
                    return new Animation(item, properties, settings, _continue);
                };
            }
        }



        /**
         *  {{#crossLink "Tween"}}{{/crossLink}}s used by the Animation.
         *  @property {Array} tweens
         */
        self.tweens = [];
        /**
         *  If the Animation is in `onFrame` mode :
         *  Identifier of the {{#crossLink "frameMamanger"}}{{/crossLink}} callback called on every tick.
         *  @property {String} ticker
         *  @readonly
         */
        self.ticker = null;
        /**
         *  Callback used when queueing animations.
         *  @property {Function} _continue
         *  @readonly
         *  @private
         */
        self._continue = _continue;

        // store the reference to the animation in the item's data
        if (typeof self.itemForAnimations.data === "undefined") {
            self.itemForAnimations.data = {};
        }
        if (typeof self.itemForAnimations.data._animatePaperAnims === "undefined") {
            self.itemForAnimations.data._animatePaperAnims = [];
        }
        /**
         *  Index of the animation in the item's queue.
         *  @property {Number} _dataIndex
         *  @readonly
         *  @private
         */
        self._dataIndex = self.itemForAnimations.data._animatePaperAnims.length;
        self.itemForAnimations.data._animatePaperAnims[self._dataIndex] = self;

        for (var i in properties) {
            if (properties.hasOwnProperty(i)) {
                self.tweens.push(new Tween(i, properties[i], self));
            }
        }

        if (self.settings.mode === "onFrame") {
            self.ticker = frameManager.add(self.itemForAnimations, "_animate" + self.startTime + (Math.floor(Math.random() * (1000 - 1)) + 1), function() {
                self.tick();
            });
        }
    }
    /**
     *  Called on each step of the animation.
     *
     *  @method tick
     */
Animation.prototype.tick = function() {
    var self = this;
    if (!!self.stopped) return false;
    var currentTime = new Date().getTime();
    if( self.startTime + self.settings.delay > currentTime ){
        return false;
    }
    var remaining = Math.max(0, self.startTime + self.settings.delay + self.settings.duration - currentTime);
    var temp = remaining / self.settings.duration || 0;
    var percent = 1 - temp;

    for (var i = 0, l = self.tweens.length; i < l; i++) {
        self.tweens[i].run(percent);
    }
    if (typeof self.settings.step !== "undefined") {
        self.settings.step.call(self.item, {
            percent: percent,
            remaining: remaining
        });
    }
    if (typeof self.settings.parentItem !== "undefined") {
        self.settings.parentItem.project.view.draw();
    } else {
        self.item.project.view.draw();
    }

    // if the Animation is in timeout mode, we must force a View update
    if (self.settings.mode === "timeout") {
        //
    }
    if (percent < 1 && l) {
        return remaining;
    } else {
        self.end();
        return false;
    }
};
/**
 *  Interrupts the animation. If `goToEnd` is true, all the properties are set to their final value.
 *  @method stop
 *  @param {Bool} goToEnd
 *  @param {Bool} forceEnd to prevent loops
 */
Animation.prototype.stop = function(goToEnd, forceEnd) {
    var self = this;
    var i = 0;
    var l = goToEnd ? self.tweens.length : 0;
    if (!!self.stopped) return self;
    self.stopped = true;
    for (; i < l; i++) {
        self.tweens[i].run(1);
    }
    if (!!goToEnd) {
        // stop further animation
        if (!!self._continue) self._continue = null;
        self.end(forceEnd);
    }
};
/**
 *  Called when the animations ends, naturally or using `.stop(true)`.
 *  @method end
 */
Animation.prototype.end = function(forceEnd) {
    var self = this;
    if (self.settings.mode === "onFrame") {
        frameManager.remove(self.itemForAnimations, self.ticker);
    }
    if (typeof self.settings.complete !== "undefined") {
        self.settings.complete.call(self.item, this);
    }

    // if the Animation is in timeout mode, we must force a View update
    if (self.settings.mode === "timeout") {
        //
    }
    if (typeof self._continue === "function") {
        self._continue.call(self.item);
    }
    // remove all references to the animation
    self.itemForAnimations.data._animatePaperAnims[self._dataIndex] = null;
    if (!!forceEnd || typeof self.repeatCallback !== "function") {
        self = null;
    } else {
        // repeat
        var newRepeat = self.repeat;
        if (self.repeat !== true) {
            newRepeat = self.repeat - 1;
        }
        return self.repeatCallback(newRepeat);
    }
};

/**
 *  Normalizes existing values from an Animation settings argument
 *  and provides default values if needed.
 *
 *  @method _initializeSettings
 *  @param {mixed} settings a `settings` object or undefined
 *  @private
 */
function _initializeSettings(settings) {
    var defaults = {
        duration: 400,
        delay: 0,
        repeat: 0,
        easing: "linear",
        complete: undefined,
        step: undefined,
        mode: "onFrame"
    };
    if (typeof settings === "undefined") {
        settings = {};
    }

    // .duration must exist, and be a positive Number
    if (typeof settings.duration === "undefined") {
        settings.duration = defaults.duration;
    } else {
        settings.duration = Number(settings.duration);
        if (settings.duration < 1) {
            settings.duration = defaults.duration;
        }
    }
    // .delay must exist, and be a positive Number
    if (typeof settings.delay === "undefined") {
        settings.delay = defaults.delay;
    } else {
        settings.delay = Number(settings.delay);
        if (settings.delay < 1) {
            settings.delay = defaults.delay;
        }
    }

    // .repeat must exist, and be a positive Number or true
    if (typeof settings.repeat === "undefined") {
        settings.repeat = defaults.repeat;
    }
    else if (typeof settings.repeat === "function") {
        // ok
    } else {
        if (settings.repeat !== true) {
            settings.repeat = Number(settings.repeat);
            if (settings.repeat < 0) {
                settings.repeat = defaults.repeat;
            }
        }
    }

    // .easing must be defined in `easing`
    if (typeof settings.easing === "undefined") {
        settings.easing = defaults.easing;
    }
    if (typeof easing[settings.easing] !== "undefined" && easing.hasOwnProperty(settings.easing)) {
        settings.easingFunction = easing[settings.easing];
    } else {
        settings.easing = defaults.easing;
        settings.easingFunction = easing[defaults.easing];
    }


    // callbacks must be functions
    if (typeof settings.complete !== "function") {
        settings.complete = undefined;
    }
    if (typeof settings.step !== "function") {
        settings.step = undefined;
    }

    // .mode must be either "onFrame" or "timeout"
    if (["onFrame", "timeout"].indexOf(settings.mode) === -1) {
        settings.mode = defaults.mode;
    }

    return settings;
}

module.exports = Animation;