git @ Cat's Eye Technologies HTML5-Gewgaws / master fingerspelling / yoob / animation.js
master

Tree @master (Download .tar.gz)

animation.js @masterraw · history · blame

/*
 * This file is part of yoob.js version 0.7
 * Available from https://github.com/catseye/yoob.js/
 * This file is in the public domain.  See http://unlicense.org/ for details.
 */
if (window.yoob === undefined) yoob = {};

/*
 * Pretty standard shim to get window.{request,cancelRequest}AnimationFrame
 * functions, synthesized from the theory and the many examples I've seen.
 */

window.requestAnimationFrame =
    window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.oRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    function(f, elem) {
        return setTimeout(function() {
            f(Date.now());
        }, 1000 / 60);
    };

// it was called "cancelRequestAnimationFrame" in the editor's draft:
// http://webstuff.nfshost.com/anim-timing/Overview.html
// but "cancelAnimationFrame" in the Candidate Recommendation:
// http://www.w3.org/TR/animation-timing/
window.cancelAnimationFrame =
    window.cancelAnimationFrame ||
    window.webkitCancelAnimationFrame ||
    window.mozCancelAnimationFrame ||
    window.oCancelAnimationFrame ||
    window.msCancelAnimationFrame ||
    window.cancelRequestAnimationFrame ||
    window.webkitCancelRequestAnimationFrame ||
    window.mozCancelRequestAnimationFrame ||
    window.oCancelRequestAnimationFrame ||
    window.msCancelRequestAnimationFrame ||
    clearTimeout;
window.cancelRequestAnimationFrame = window.cancelAnimationFrame;

/*
 * A yoob.Animation object manages an animation.
 *
 * How many things get animated by one yoob.Animation object is up to
 * you.  For animated demos, it may be sufficient to have only one
 * Animation object which updates many independent graphical objects.
 * However, it may be useful to have multiple Animation objects, if
 * the program can be in different states (for example, one title screen
 * Animation, and a different Animation to use during gameplay.)
 */
yoob.Animation = function() {
    /*
     * Initialize a yoob.Animation.  Takes a configuration dictionary:
     *
     *   mode          'quantum' (default) or 'proportional'
     *   object        the object to call methods on
     *   tickTime      in msec.  for quantum only. default = 1/60th sec
     *   lastTime      internal (but see below)
     *   accumDelta    internal, only used in quantum mode
     *
     * There are two modes that a yoob.Animation can be in,
     * 'quantum' (the default) and 'proportional'.
     *
     * Once the Animation has been started (by calling animation.start()):
     *
     * In the 'quantum' mode, the object's draw() method is called on
     * each animation frame, and the object's update() method is called as
     * many times as necessary to ensure it is called once for every tickTime
     * milliseconds that have passed.  Neither method is passed any
     * parameters.
     *
     * update() (or draw(), in 'proportional' mode only) may return the 
     * exact object 'false' to force the animation to stop immediately.
     *
     * In the 'proportional' mode, the object's draw() method is called on
     * each animation frame, and the amount of time (in milliseconds) that has
     * elapsed since the last time it was called (or 0 if it was never
     * previously called) is passed as the first and only parameter.
     */
    this.init = function(cfg) {
        this.object = cfg.object;
        this.lastTime = cfg.lastTime || null;
        this.accumDelta = cfg.accumDelta || 0;
        this.tickTime = cfg.tickTime || (1000.0 / 60.0);
        this.mode = cfg.mode || 'quantum';
        this.request = null;
        return this;
    };

    this.start = function() {
        if (this.request) {
            return false;
        }
        var $this = this;
        if (this.mode === 'quantum') {
            var animFrame = function(time) {
                $this.object.draw();
                if ($this.lastTime === null) {
                    $this.lastTime = time;
                }
                $this.accumDelta += (time - $this.lastTime);
                while ($this.accumDelta > $this.tickTime) {
                    $this.accumDelta -= $this.tickTime;
                    var result = $this.object.update();
                    if (result === false) {
                        $this.accumDelta = $this.tickTime;
                        $this.request = null;
                    }
                }
                $this.lastTime = time;
                if ($this.request) {
                    $this.request = requestAnimationFrame(animFrame);
                }
            };
        } else if (this.mode === 'proportional') {
            var animFrame = function(time) {
                var timeElapsed = (
                    $this.lastTime == null ? 0 : time - $this.lastTime
                );
                $this.lastTime = time;
                var result = $this.object.draw(timeElapsed);
                if (result === false) {
                    $this.request = null;
                }
                if ($this.request) {
                    $this.request = requestAnimationFrame(animFrame);
                }
            };
        }
        this.request = requestAnimationFrame(animFrame);
        return true;
    };

    this.stop = function() {
        if (this.request) {
            cancelRequestAnimationFrame(this.request);
        }
        this.request = null;
    };

    this.reset = function() {
        this.stop();
        this.lastTime = null;
    };
};