AnimeJ: a Javascript animation library

AnimeJ.js

Summary

This is the complete AnimeJ library, an animation for DHTML designed to be lightweight and efficient. This library has been developed for Toscana4U.

Version: 1.0

Author: Antonio Cisternino cisterni@di.unipi.it (C) 2008, University of Pisa


Class Summary
AnimeJInterp This class contains helpers for building interpolation tasks.
Timeline The timeline is the main interface to access the services of the animation library.

Method Summary
static void AnimeSetOpacity(<Object> obj, <float> value)
           Setting the opacity is becoming more complicated (IE8 changed how alpha is handled and still does not support the opacity attribute defined in CSS3).
static void BlueScreen(<String> msg)
           Displays a blue screen inspired to Windows well known blue screen.
static void RegisterScript(<String> name, <int> version, <string> description)
           This function should be called by scripts to register their loading.
static void Require(<Array> scripts)
           This function is meant to check dependencies among a number of JavaScript source files that share this dependency convention.

/** 
 * @fileoverview This is the complete {@link http://www.codeplex.com/animej AnimeJ}
 * library, an animation for DHTML designed to be lightweight and efficient.
 * This library has been developed for {@link http://www.toscana4u.net/ Toscana4U}.
 *
 * @author Antonio Cisternino cisterni@di.unipi.it (C) 2008, University of Pisa
 * @version 1.0
 */

/**
 * @private
 */
var __DependenciesManager;

/**
 * Displays a blue screen inspired to Windows well known blue screen. The modal
 * popup is closed when you click on it.
 * @param {String} msg Message to display (preceded by 'INTERNAL ERROR: ')
 */
function BlueScreen(msg) {
  if (document.body) {
    alert('onloaded');
    var d = document.createElement('div'), k;
    var values = { 'position': 'absolute', 'top': 0, 'left': 0, 'width': '100%',
      'height': '100%', 'background': 'Blue', 'color': 'White', 'fontFamily': 'Fixedsys, monospace' };
    for (k in values) d.style[k] = values[k];
    d.innerHTML = 'INTERNAL ERROR: '+msg;
    d.onclick = function () { document.body.removeChild(d); };
    document.body.appendChild(d);
  } else {
    // Hack! when in header you cannot access to DOM
    document.write('<div style="z-index: 1000; position: absolute; left: 0; top: 0; margin: 0; padding: 0; width: 100%; height: 100%; background: Blue; color: White; font-family: Fixedsys, monospace" onclick="this.parentNode.removeChild(this);">INTERNAL ERROR: '+msg+'</div>');
  }
  throw "INTERNAL ERROR: " + msg;
}

/**
 * This function should be called by scripts to register their loading.
 * @param {String} name Name of the script
 * @param {int} version Version of the script, should be an increasing integer number
 * @param {string} description Brief description used for display purpose
 */
function RegisterScript(name, version, description) {
  if (! __DependenciesManager) __DependenciesManager = {};
  __DependenciesManager[name + ", version=" + version] = description;
}

/**
 * This function is meant to check dependencies among a number of JavaScript
 * source files that share this dependency convention. If a dependency is violated
 * the script displays a blue screen informing which scripts are not available.
 * @param {Array} scripts The required scripts. Each element of the array can be either
 *        a string with the script name (registered using RegisterScript) or a pair
 *        { Name: '...', Version: 0 } if the least version number is specified.
 */
function Require(scripts) {
  var i, msg = null;
  for (i = 0; i < scripts.length; i++) {
    var el = scripts[i], k, strong = typeof(el) != "string";
    if (strong && !el.Name) {
      msg = "Invalid require statement!";
      BlueScreen(msg);
      return;
    }
    var toadd = strong ? el.Name + ' (version ' + el.Version + ')' : el;
    for (k in __DependenciesManager)
      if (!strong) {
        if (k.indexOf(el) == 0) {
          toadd = null;
          break;
        }
      } else {
        if (k.indexOf(el.Name) == 0) {
          var v = parseInt(k.substring(k.indexOf(", version=") + 10));
          if (el.Version && el.Version <= v) {
            toadd = null;
            break;
          }
        }
      }
    if (toadd)
      if (msg) msg += ", " + toadd;
      else msg = toadd;
  }
  
  if (msg) BlueScreen('Missing libraries are <br><br>' + msg);
}

RegisterScript('AnimeJ', 1, 'AnimeJ animation library');

/**
 * @private
 * @class Heap implementation, it is used to define a shared timer which is responsible
 * for orchestrating the animation. This class is considered to be private to the library.
 * @constructor
 */
function AnimeJHeap() {
  // This is used to close this in local functions.
  var obj = this;

  // Current position of the heap index.
  var pos = 0;

  // Function to compute the left node of the tree.
  var left = function(idx) {
    return (2 * (idx + 1) - 1);
  }

  // Function to compute the right node of the tree.
  var right = function (idx) {
    return (2 * (idx + 1));
  }

  // Function to compute the parent node of a node.
  var parent = function (idx) {
    return (Math.floor((idx + 1) / 2) - 1);
  }
  
  // Function to swap the content of two nodes.
  var swap = function (a, b) {
    var tmp = obj[a];
    obj[a] = obj[b];
    obj[b] = tmp;
  }
  
  // Function to percolate up a node in the heap.
  var percolateUp = function (p) {
	var par = parent(p);
	while ((p > 0) && (obj[p].Due < obj[par].Due)) {
		swap(p, par);
		p = par;
		par = parent(p);
	}
  }
  
  // Function to percolate down a node in the heap.
  var percolateDown = function (p) {
	var l = left(p), r = right(p);
	if (l < pos) {
		if (r < pos) {
			if (obj[l].Due > obj[r].Due) {
				swap(p, r);
				percolateDown(r);
			} else {
				swap(p, l);
				percolateDown(l);
			}
		} else {
			swap(p, l);
			percolateDown(l);
		}
	} else if (p != pos - 1) {
		swap(p, pos - 1);
		percolateUp(p);
	}
  }
  
  /**
   * The number of elements contained in the heap.
   * @type int
   */
  this.Count = function () { return pos; }

  /**
   * Insert a Task into the heap.
   * @param {AnimeJTask} el Task to be inserted.
   */  
  this.Insert = function (el) {
    this[pos] = el;
    percolateUp(pos++);
  }

  /**
   * Read the top of the heap. If the heap is empty null is returned.
   * @type AnimeJTask
   */  
  this.Top = function () {
	if (pos)
		return this[0];
	return null;
  }

  /**
   * Remove the top element from the heap and returns it. If the heap
   * is empty null is returned and the heap is left unchanged.
   * @type AnimeJTask
   */  
  this.Remove = function () {
	var ret = null;
	if (!pos) return ret;
	ret = this[0];

	percolateDown(0);
	this[pos--] = null;

	return ret;
  }
  
  /**
   * Remove a specific task from the heap.
   * @param {AnimeJTask} t The task to remove. 
   * @type AnimeJTask
   */
  this.RemoveTask = function (t) {
    var i, ret = -1;
    for (i = 0; i < pos; i++)
      if (this[i].Task == t) {
        ret = this[i].Due;
        percolateDown(i);
	    this[pos--] = null;
        break;
      }
    return ret;
  }
  
  /**
   * Debug function to convert the heap into a string.
   * @private
   * @type String
   */
  this.ToString = function () {
    var i, ret = "";
    for (i = 0; i < pos; i++)
      ret += " <i>" + i + ":</i> " + this[i].Due;
    return ret;
  }
}


/**
 * @private
 * @class This is the base class for all tasks that can be scheduled
 * by the library. It simply defines the expected structure of an object
 * that is handled by the scheduler.
 * The model chosen by the library is that of Finite State Automata (FSA):
 * each activity is an automata that is notified as time passes and of other
 * events.
 */
var AnimeJTaskPrototype = {
  /**
   * Perform a step of computation
   */
  DoAction: function (delay) {}
}

/**
 * @private
 * @class This is the base class for all tasks that can be scheduled
 * by the library. It simply defines the expected structure of an object
 * that is handled by the scheduler.
 * The model chosen by the library is that of Finite State Automata (FSA):
 * each activity is an automata that is notified as time passes and of other
 * events.
 */
var AnimeJInterpolatedTaskPrototype = {
  /**
   * Notify the task that the animation has been paused.
   */
  OnPause: function () {},
  /**
   * Notify the task that the animation has been resumed.
   */
  OnResume: function () {},
  /**
   * Notify the task that the animation has been stopped.
   */
  OnStop: function () {}
}

AnimeJInterpolatedTaskPrototype.prototype = AnimeJTaskPrototype;

/**
 * Global variable to keep track of the current timer.
 */ 
var AnimeJTimerRunning = null;

/**
 * @private
 * @class This class implements a shared timer using a heap and the
 * setTimeout javascript function. Usually this class is not used by
 * the public API and it is used by the library.
 */
function AnimeJTimer() {
  /**
   * @private
   * When the timer is started, it is used to record time.
   */
  this.Start = new Date();

  /**
   * @private
   * Heap used to store tasks ordered by due time.
   */
  this.Heap = new AnimeJHeap();

  /**
   * Return the timer time, a number from start time to
   * the current time.
   * @type int
   */  
  this.Time = function () {
    return new Date() - this.Start;
  }

  /**
   * Schedules a task for execution.
   * @param {AnimeJTask} what Task to be executed
   * @param {int} when Number of milliseconds to schedule the task.
   */
  this.SetAlertMillis = function(what, when) {
    var exp = when + this.Time();
    this.Heap.Insert({ 'Due': exp, 'Task': what });
    if (AnimeJTimerRunning != null && this.Heap.Top().Due == exp) {
      clearTimeout(AnimeJTimerRunning);
      AnimeJTimerRunning = null;
    }
    if (AnimeJTimerRunning == null) {
      AnimeJTimerRunning = true;
      var tosleep = this.Heap.Top().Due - this.Time();
      tosleep = 0 > tosleep ? 1 : tosleep;
      AnimeJTimerRunning = setTimeout('AnimeJTimerTick()', tosleep);
    }
  }
  
  /**
   * Remove a task from the scheduler. If the task is not present it does nothing.
   * @param {AnimeJTask} t Task to be removed.
   */ 
  this.RemoveTask = function (t) {
    var v = this.Heap.RemoveTask(t);
    if (v > -1) {
      v = v - this.Time();
      if (v < 0) v = 0;
    }
    return v;
  }
  
  /**
   * Schedules a task for execution.
   * @param {AnimeJTask} what Task to be executed
   * @param {int} when Absolute time at which the task should be scheduled.
   */
  this.SetAlertDate = function (what, when) {
    var t = when - this.Start;
    this.SetAlertMillis(what, t);
  }
}

/**
 * Global timer used by the library for all the animations.
 */
var AnimeJGlobalTimer = new AnimeJTimer();

/**
 * @private
 * Helper function that is used by the setTimeout function to invoke
 * the timer.
 */
function AnimeJTimerTick() {
  var t = AnimeJGlobalTimer;
  var currt = t.Time() + 10;
  while (t.Heap.Count() > 0 && currt >= t.Heap.Top().Due) {
    var el = t.Heap.Remove();
    el.Task.DoAction(currt - el.Due);
  }
  if (t.Heap.Count() > 0) {
    currt = t.Time();
    AnimeJTimerRunning = setTimeout('AnimeJTimerTick()', t.Heap.Top().Due - currt);
  } else {
    AnimeJTimerRunning = null;
  }
}

/**
 * @private
 * @class Conversion functions used by interpolators. They interpolate
 * a range of values using a parameter between 0.0 and 1.0 which
 * represents a time fraction.
 */
function AnimeJConv() {}

/**
 * Interpolates a single integer value
 * @param {int} from Starting value
 * @param {int} to Ending value
 * @param {float} v A fraction between 0.0 and 1.0
 * @type int
 * @final
 */
AnimeJConv.Int = function (from, to, v) {
    return Math.floor(from + (to - from)*v);
  };

/**
 * Interpolates a single float value
 * @param {float} from Starting value
 * @param {float} to Ending value
 * @param {float} v A fraction between 0.0 and 1.0
 * @type float
 * @final
 */
AnimeJConv.Float = function (from, to, v) {
    return from + (to - from)*v;
  };

/**
 * Interpolates between two objects in a discrete fashion: if v is
 * less than 0.5 the first is returned the second otherwise.
 * @param {Object} from Starting value
 * @param {Object} to Ending value
 * @param {float} v A fraction between 0.0 and 1.0
 * @type Object
 * @final
 */
AnimeJConv.Discrete = function (from, to, v) {
    return v < 0.5 ? from : to;
  };
  
/**
 * Interpolates an array of integers
 * @param {Array} from Starting value
 * @param {Array} to Ending value
 * @param {float} v A fraction between 0.0 and 1.0
 * @type Array
 * @final
 */
AnimeJConv.IntList = function (from, to, v) {
    var ret = new Array(), i;
    for (i = 0; i < from.length; i++)
      ret.push(Math.floor(from[i] + (to[i] - from[i])*v));
    return ret;
  };

/**
 * Interpolates an array of floats
 * @param {Array} from Starting value
 * @param {Array} to Ending value
 * @param {float} v A fraction between 0.0 and 1.0
 * @type Array
 * @final
 */
AnimeJConv.FloatList = function (from, to, v) {
    var ret = new Array(), i;
    for (i = 0; i < from.length; i++)
      ret.push(from[i] + (to[i] - from[i])*v);
    return ret;
  };


/**
 * @private
 * @class Generalized linear interpolator used to interpolate
 * the argument of a function or an object property like a style.
 * The interpolation is linear but it is possible to specify an array of
 * linear segments unevenly distributed in the interval [0.0, 1.0] which
 * is the range of time (normalized). For instance the following is a way to
 * distribute interpolation for a value from 0 to 1000 in a way that the first
 * half the value changes linearly in the interval 0-50 and in the second half
 * in the interval 50-1000:<br/><br/>
 * [ { t: 0.0, v: 0 }, { t: 0.5, v: 50 }, { t: 1.0, v: 1000 } ]
 * @constructor
 * @param {Object} obj Either an object (like a style of a DOM node) or a function
 * with a single parameter which gets invoked for each change of the value.
 * @param {String} prop The property name to be changed, it is considered only in case
 * the obj parameter refers to an object.
 * @param {Array} steps Array of pairs { t: t, v: v } ORDERED by the field t. The
 * t field is a time value and should be in the interval [0.0, 1.0] and the first
 * and the last values for t MUST be 0.0 and 1.0 respectively. The field v defines the
 * value that should be set when time reaches the given value. Interpolation of values
 * is performed 
 * @param {Function} conv Conversion function to interpolate two values given a time
 * value in the range [0.0, 1.0] like those defined in AnimeJConv
 * @param {String} prefix Prefix string for the output value.
 * @param {String} suffix Suffix string for the output value (for instance 'px').
 * @exception If steps.length is less than two, or the sequence of steps is unordered
 * with respect to time, or does not begin by 0.0 nor end with 1.0.
 * @see AnimeJConv
 */
function AnimeJLinearInterpolator(obj, prop, steps, conv, prefix, suffix) {
  var i, last = 0.0;
  // typecheck steps
  if (steps.length < 2) throw "Invalid steps: length less than 2";
  if (steps[0].t != 0.0) throw "Invalid steps: timeline must start at 0.0!";
  if (steps[steps.length - 1].t != 1.0) throw "Invalid steps: timeline must end with 1.0!";
  for (i = 1; i < steps.length; i++) {
    if (steps[i].t > 1.0 || steps[i].t < 0.0 || steps[i].t <= last)
      throw "Invalid steps: invalid value "+(steps[i].t) +"!";
    last = steps[i].t;
  }

  /**
   * Computes a value given a time value in [0.0, 1.0]
   * @param {float} v Time value in the interval [0.0, 1.0]
   */
  this.Compute = function (v) {
    var idx;
    for (idx = 1; idx < steps.length; idx++)
      if (steps[idx].t >= v) break;
    
    idx--;
    var val = conv(steps[idx].v, steps[idx + 1].v, (v - steps[idx].t)/(steps[idx + 1].t - steps[idx].t));
    if (typeof(obj) == 'function')
      obj(val);
    else
      obj[prop] = prefix + val + suffix;
  }
}

/**
 * @private
 * @class Task for scheduling interpolators on the timer library.
 * The task is not trivial because it supports suspension and
 * resuming of tasks that means changing the absolute deadlines
 * inside the heap.
 * @constructor
 * @param {AnimeJLinearInterpolator} interp Interpolator to be used,
 * it can be an object with the Compute(v) function.
 * @param {int} ms Duration of the transition between 0.0 and 1.0 (and
 * consequently of the associated values).
 * @param {int} stepms The number of milliseconds between each value transition
 * (i.e. how frequent the value is changed).
 * @base AnimeJTask
 * @see AnimeJLinearInterpolator
 * @see AnimeJTimer
 */
function AnimeJInterpolatedTask(interp, ms, stepms) {
  this.prototype = AnimeJInterpolatedTaskPrototype;

  /**
   * Duration of the whole transition in milliseconds.
   * @type {int}
   */
  this.Duration = ms;
  /**
   * Offset used to take into account suspension of task execution.
   * @type {int}
   */
  this.Start = - 1;
  
  
  /**
   * If true interpolators are invoked with 1 - v rather than v.
   * @type {bool}
   */  
  this.Reverse = false;

  /**
   * Invoked when the task is suspended, updates the internal state.
   */  
  this.OnPause = function() {
    if (this.Start != -1)
      this.Start = AnimeJGlobalTimer.Time() - this.Start;
  }
  
  /**
   * Invoked when the task is resumed, updates the internal state.
   */  
  this.OnResume = function() {
    if (this.Start != -1)
      this.Start = AnimeJGlobalTimer.Time() - this.Start;
  }
  
  /**
   * Invoked when the task is stopped, updates the internal state.
   */  
  this.OnStop = function() {
    this.Start = -1;
  }
    
  /**
   * It performs a step of animation by invoking the interpolator.
   * @param {int} d Delay in the notification with respect to the original deadline set.
   */  
  this.DoAction = function (d) {
    if (this.Start == -1)
      this.Start = AnimeJGlobalTimer.Time();
    
    var t = AnimeJGlobalTimer.Time();
    var v = (t - this.Start) / this.Duration;
    v = v > 1 ? 1 : v;
    interp.Compute(this.Reverse ? 1 - v : v);
    var t1 = AnimeJGlobalTimer.Time();
    var next = stepms - d + t1 - t; 
    if (v < 1)
      AnimeJGlobalTimer.SetAlertMillis(this, next);
    else
      this.Start = -1;
  }
}

/**
 * @private
 * @class Task used by the timeline to fire callbacks at the end of a timeline execution.
 * @constructor
 * @param {Timeline} t Timeline to be used to callback.
 * @param {String} prop Name of the property of the timeline holding the callback function.
 * @base AnimeJTask
 * @see AnimeJLinearInterpolator
 * @see AnimeJTimer
 */
function AnimeJTimelineCallBackTask(t, prop) {
  this.prototype = AnimeJTaskPrototype;

  /**
   * It invokes the specified callback on the property prop of the timeline t if defined.
   * @param {int} d Delay in the notification with respect to the original deadline set.
   */  
  this.DoAction = function (d) {
    if (t[prop])
      (t[prop])(d);
  }
}

/**
 * @private
 * @class Task used to invoke arbitrary functions when the task gets executed. The task is
 * fired only once.
 * @constructor
 * @param {Function} fun Function to be invoked when the task is executed, it may accept an integer
 * argument informing the delay of the execution with the expected deadline.
 * @base AnimeJTask
 * @see AnimeJLinearInterpolator
 * @see AnimeJTimer
 */
function AnimeJFunctionCallbackTask(fun) {
  this.prototype = AnimeJTaskPrototype;

  /**
   * It performs a step of animation by invoking the interpolator.
   * @param {int} d Delay in the notification with respect to the original deadline set.
   */  
  this.DoAction = function (d) {
    fun(d);
  }
}


/**
 * @class The timeline is the main interface to access the services of the animation library.
 * It features the classic interface to timeline in which tasks are scheduled at a given time
 * relative to the start of the execution. The SetAt method is meant for this purpose, and
 * an AnimeJTask should be provided. Usually the task is generated by one of the functions in
 * the AnimeJInterp class.<br/>
 * In the following example we have a text box that can be collapsed to left and vice versa.
 * This is the complete source code to show how expressive the library is:
 * <pre>
 * &lt;html&gt;
 * &lt;head&gt;
 * &lt;title&gt;Auto-hide text box&lt;/title&gt;
 * &lt;script type="text/javascript" src="..\..\src\AnimeJ.js"&gt;&lt;/script&gt;
 * &lt;script&gt;
 * function transition(btn) {
 *   var txt = btn.parentNode.childNodes[0];
 *   var t = new Timeline();
 *   if (txt.style.display == 'none') {
 *     txt.style.display = 'inline';
 *     t.SetAt(0, AnimeJInterp.px(1000, 30, txt.style, 'width', 0, 120));
 *     t.SetAt(0, AnimeJInterp.alpha(1000, 30, txt.style, 0.0, 1.0));
 *     t.OnEnd = function () { btn.innerHTML = '&amp;lt;&amp;lt;'; };
 *   } else {
 *     t.SetAt(0, AnimeJInterp.px(1000, 30, txt.style, 'width', 120, 0));
 *     t.SetAt(0, AnimeJInterp.alpha(1000, 30, txt.style, 1.0, 0.0));
 *     t.OnEnd = function (d) { btn.innerHTML = '&amp;gt;&amp;gt;'; txt.style.display = 'none'; };
 *   }
 *   t.Run();
 * }
 * &lt;/script&gt;
 * &lt;/head&gt;
 * &lt;body&gt;
 *   &lt;span&gt;
 *     &lt;input type="text" width="120px"&gt;&lt;/input&gt;
 *     &lt;span style="cursor: pointer; color: Blue" onclick="transition(this)"&gt;&amp;lt;&amp;lt;&lt;/span&gt;
 *   &lt;/span&gt;
 * &lt;/body&gt;
 * &lt;/html&gt;
 * </pre>
 * Note that the transition function simply prepares a timeline object t with a pixel interpolation
 * that changes the width property of the text box from 120 to 0 and vice versa. We also use fading by
 * changing the alpha during transition. Since the two transitions start at time 0 they run concurrently.
 * The OnEnd callback is used to update the text close to the text box.
 * @constructor
 * @see AnimeJInterp
 */
function Timeline() {
  var Paused = 0;
  var OnEndCB = null;
  var Data = {};
  var Running = false;
  var Reverse = false;
  
  /**
   * Tells wether the timeline is paused or not.
   * @type {bool}
   */
  this.IsPaused = function () {
    return Paused != 0;
  }

  
  /**
   * Check if the timeline is running.
   * Note that this will return true even if the timeline is paused. Use IsPaused()
   * to discriminate the actual status.
   * @type {bool}
   */
  this.IsRunning = function () {
    return Running;
  }

  /**
   * @private
   * Internal function used to raise the onend notification.
   */
  this.InternalOnEnd = function (d) {
    Runnning = false;
    if (this['OnEnd']) this['OnEnd'](d, Reverse);
  }

  /**
   * Executes the timeline.
   * @param {bool} If true interpolators run reversed.
   */  
  this.Run = function (reverse) {
    Reverse = reverse ? true : false;
    Running = true;
    var max = 0;
    for (v in Data) {
      var el = Data[v];
      var startAt = parseInt(v);
      var i = 0;
      if (startAt > max) max = startAt;
      for (i = 0; i < el.length; i++) {
        el[i].Reverse = Reverse;
        if (el[i].Duration && (startAt + el[i].Duration) > max) max = startAt + el[i].Duration;
        AnimeJGlobalTimer.SetAlertMillis(el[i], startAt);
      }
    }
   var tm = this;
   OnEndCB = new AnimeJTimelineCallBackTask(tm, 'InternalOnEnd');
   AnimeJGlobalTimer.SetAlertMillis(OnEndCB, max);
  }

  /**
   * Schedule a task for execution at a given time from the start of the timeline.
   * @param {int} timems Milliseconds to be elapsed since the beginning of execution
   * before executing the task anim.
   * @param {AnimeJInterpolatedTask} task Task to be executed after timems elapsed.
   */
  this.SetAt = function (timems, task) {
    if (Data[timems])
      Data[timems].push(task);
    else
      Data[timems] = new Array(task);
  }

  /**
   * Stops the execution of the timeline by removing all the tasks from the scheduler.
   * The timeline should be started by invoking Run().
   */
  this.Stop = function () {
    Running = false;
    Paused = 0;
    var t = AnimeJGlobalTimer;
    var tm, i, v; 
    if (OnEndCB != null) {
      tm = t.RemoveTask(OnEndCB);
      OnEndCB = null;
    }
    
    for (v in Data) {
      var el = Data[v];
      for (i = 0; i < el.length; i++) {
        tm = t.RemoveTask(el[i]);
        el[i].OnStop();
      }
    }
  }


  /**
   * Pauses the execution of the current timeline. It can be resumed later on by invoking
   * the Resume() method.
   */  
  this.Pause = function () {
    if (Paused) return;
    Paused = 1;
    var t = AnimeJGlobalTimer;
    var tm, i, v; 
    if (OnEndCB != null) {
      tm = t.RemoveTask(OnEndCB);
      if (tm > -1)
        OnEndCB.Paused = tm;
      else
        OnEndCB = null;
    }
    
    for (v in Data) {
      var el = Data[v];
      for (i = 0; i < el.length; i++) {
        tm = t.RemoveTask(el[i]);
        if (tm == 0) tm = 1;// Hack: Paused == 0 is overloaded.
        el[i].OnPause();
        if (tm > -1)
          el[i].Paused = tm;
      }
    }
  }

  /**
   * Resumes the execution of a paused timeline.
   * @exception An exception is raised if the timeline is not paused.
   */
  this.Resume = function () {
    if (!Paused) throw "Timeline not paused!";
    Paused = 0;
    var t = AnimeJGlobalTimer;
    var tm, i, v; 
    if (OnEndCB != null) {
      t.SetAlertMillis(OnEndCB, OnEndCB.Paused);
      OnEndCB.Paused = 0;
    }
    
    for (v in Data) {
      var el = Data[v];
      for (i = 0; i < el.length; i++) {
        if (el[i].Paused) {
          t.SetAlertMillis(el[i], el[i].Paused);
          el[i].Paused = 0;
        }
        el[i].OnResume();
      }
    }
  }  
}

/**
 * Browser test. Used for setting alpha, but it can be useful in many other situations...
 */
var AnimeIsIE = navigator.appVersion.indexOf("MSIE") != -1;
var AnimeIEVer = AnimeIsIE ? parseFloat(navigator.appVersion.match(/MSIE\s([0-9\.]+)/)[1]) : -1;

/**
 * Setting the opacity is becoming more complicated (IE8 changed how alpha is handled
 * and still does not support the opacity attribute defined in CSS3). This function
 * is meant to hide these differences. Note that you must provide not the style object
 * but the whole object
 * @param {Object} obj Node to which the opacity should be applied
 * @param {float} value Opacity value expressed in 0-1 interval as CSS3 standard requires.
 */
function AnimeSetOpacity(obj, value) {
  if (AnimeIsIE) {
    //obj.filters.item("DXImageTransform.Microsoft.Alpha").Opacity = Math.floor(value * 100);
    obj.style.filter = "alpha(opacity=" + (Math.floor(value * 100)) + ")";
  } else {
    obj.style.opacity = value;
  }
}

/**
 * @class This class contains helpers for building interpolation tasks. It simply uses the
 * AnimeJLinearInterpolator in several different ways to ease common tasks. You can still
 * define your own interpolators. Apologize for interpolator names but they are conceived
 * to be short.
 */
function AnimeJInterp() {}

/**
 * Interpolates a pixel quantity (i.e. integer and appends px at the end).
 * @param {int} time Duration of the transition in milliseconds.
 * @param {int} tsteps Frequency (expressed in ms for an interval) at which
 * the property should be updated or the function called.
 * @param {Object} obj Object to set or function to invoke to set the value.
 * @param {Object} fromOrSteps If a number is given this is considered the starting value,
 * otherwise an array of steps is assumed as expected by AnimeJLinearInterpolator.
 * @param {int} to If specified (i.e. the fromOrSteps argument is an integer) it is
 * considered the target value for the interpolation.
 * @see AnimeJLinearInterpolator
 */
AnimeJInterp.px = function (time, tstep, obj, prop, fromOrSteps, to) {
    if (typeof(fromOrSteps) == 'number')
      fromOrSteps = [ { t: 0.0, v: fromOrSteps}, { t: 1.0, v: to } ];
    return new AnimeJInterpolatedTask(new AnimeJLinearInterpolator(obj, prop, fromOrSteps, AnimeJConv.Int, '', 'px'), time, tstep);
  };

  // Transparency from 0.0 to 1.0!
/**
 * Interpolates the alpha of an element in the appropriate way for the current browser.
 * @param {int} time Duration of the transition in milliseconds.
 * @param {int} tsteps Frequency (expressed in ms for an interval) at which
 * the property should be updated or the function called.
 * @param {Object} obj The object holding the alpha property to be changed.
 * @param {Object} fromOrSteps If a number between 0.0 (fully transparent) and 1.0
 * (totally opaque) is provided, this is considered the starting value;
 * otherwise an array of steps is assumed as expected by AnimeJLinearInterpolator.
 * @param {float} to If specified (i.e. the fromOrSteps argument is an integer) it is
 * considered the target value for the interpolation (should be a value in the range [0.0, 1.0].
 * @see AnimeJLinearInterpolator
 */
AnimeJInterp.alpha = function (time, tstep, obj, fromOrSteps, to) {
    if (typeof(fromOrSteps) == 'number')
      fromOrSteps = [ { t: 0.0, v: fromOrSteps}, { t: 1.0, v: to } ];
    
    var change = function (v) {
      AnimeSetOpacity(obj, v);
    }
    
    return new AnimeJInterpolatedTask(new AnimeJLinearInterpolator(change, null, fromOrSteps, AnimeJConv.Float, '', ''), time, tstep);
  };
  
/**
 * Interpolates an integer value and pass it to a funciton (fiv: function integer value).
 * @param {int} time Duration of the transition in milliseconds.
 * @param {int} tsteps Frequency (expressed in ms for an interval) at which
 * the property should be updated or the function called.
 * @param {Function} fun Function to be invoked with the current value.
 * @param {Object} fromOrSteps If a number is given this is considered the starting value,
 * otherwise an array of steps is assumed as expected by AnimeJLinearInterpolator.
 * @param {int} to If specified (i.e. the fromOrSteps argument is an integer) it is
 * considered the target value for the interpolation.
 * @see AnimeJLinearInterpolator
 */
AnimeJInterp.fiv = function (time, tstep, fun, fromOrSteps, to) {
    if (to)
      fromOrSteps = [ { t: 0.0, v: fromOrSteps}, { t: 1.0, v: to } ];
    return new AnimeJInterpolatedTask(new AnimeJLinearInterpolator(fun, null, fromOrSteps, AnimeJConv.Int, '', ''), time, tstep);
  };

/**
 * Interpolates a float value and pass it to a funciton (ffv: function float value).
 * @param {int} time Duration of the transition in milliseconds.
 * @param {int} tsteps Frequency (expressed in ms for an interval) at which
 * the property should be updated or the function called.
 * @param {Function} fun Function to be invoked with the current value.
 * @param {Object} fromOrSteps If a number is given this is considered the starting value,
 * otherwise an array of steps is assumed as expected by AnimeJLinearInterpolator.
 * @param {float} to If specified (i.e. the fromOrSteps argument is a float) it is
 * considered the target value for the interpolation.
 * @see AnimeJLinearInterpolator
 */
AnimeJInterp.ffv = function (time, tstep, fun, fromOrSteps, to) {
    if (to)
      fromOrSteps = [ { t: 0.0, v: fromOrSteps}, { t: 1.0, v: to } ];
    return new AnimeJInterpolatedTask(new AnimeJLinearInterpolator(fun, null, fromOrSteps, AnimeJConv.Float, '', ''), time, tstep);
  };

/**
 * Interpolates an array of integers and pass it to a funciton (fia: function int array).
 * @param {int} time Duration of the transition in milliseconds.
 * @param {int} tsteps Frequency (expressed in ms for an interval) at which
 * the property should be updated or the function called.
 * @param {Function} fun Function to be invoked with the current value.
 * @param {Array} fromOrSteps If the to argument is given this is considered the starting value,
 * otherwise an array of steps is assumed as expected by AnimeJLinearInterpolator.
 * @param {Array} to If specified (i.e. the fromOrSteps argument is considered to be an array of int) it is
 * considered the target value for the interpolation.
 * @see AnimeJLinearInterpolator
 */
AnimeJInterp.fia = function (time, tstep, fun, fromOrSteps, to) {
    if (to)
      fromOrSteps = [ { t: 0.0, v: fromOrSteps}, { t: 1.0, v: to } ];
    return new AnimeJInterpolatedTask(new AnimeJLinearInterpolator(fun, null, fromOrSteps, AnimeJConv.IntList, '', ''), time, tstep);
  };

/**
 * Interpolates an array of floats and pass it to a funciton (ffa: function float array).
 * @param {int} time Duration of the transition in milliseconds.
 * @param {int} tsteps Frequency (expressed in ms for an interval) at which
 * the property should be updated or the function called.
 * @param {Function} fun Function to be invoked with the current value.
 * @param {Array} fromOrSteps If the to argument is given this is considered the starting value,
 * otherwise an array of steps is assumed as expected by AnimeJLinearInterpolator.
 * @param {Array} to If specified (i.e. the fromOrSteps argument is considered to be an array of float) it is
 * considered the target value for the interpolation.
 * @see AnimeJLinearInterpolator
 */
AnimeJInterp.ffa = function (time, tstep, fun, fromOrSteps, to) {
    if (to)
      fromOrSteps = [ { t: 0.0, v: fromOrSteps}, { t: 1.0, v: to } ];
    return new AnimeJInterpolatedTask(new AnimeJLinearInterpolator(fun, null, fromOrSteps, AnimeJConv.FloatList, '', ''), time, tstep);
  };

AnimeJ: a Javascript animation library

Documentation generated by JSDoc on Thu Apr 15 11:14:59 2010