Anim.js

Class Summary [top]

YAHOO.util.Anim Base animation class that provides the interface for building animated effects.

Souce Code [top]

/**
 * Base class for animated DOM objects.
 * @class Base animation class that provides the interface for building animated effects.
 * <p>Usage: <code>var myAnim = new YAHOO.util.Anim(el, { width: { from: 10, to: 100 } }, 1, YAHOO.util.Easing.easeOut);</code></p>
 * @requires YAHOO.util.AnimMgr
 * @requires YAHOO.util.Easing
 * @requires YAHOO.util.Dom
 * @requires YAHOO.util.Event
 * @constructor
 * @param {HTMLElement | String} el Reference to the element that will be animated
 * @param {Object} attributes The attribute(s) to be animated.
 * Each attribute is an object with at minimum a "to" or "by" member defined.
 * Additional optional members are "from" (defaults to current value), "units" (defaults to "px").
 * All attribute names use camelCase.
 * @param {Number} duration (optional, defaults to 1 second) Length of animation (frames or seconds), defaults to time-based
 * @param {Function} method (optional, defaults to YAHOO.util.Easing.easeNone) Computes the values that are applied to the attributes per frame (generally a YAHOO.util.Easing method)
 */

// NOTE: This version modified to conform with JSDoc, not valid as src file
YAHOO.util.Anim = function(el, attributes, duration, method)
{
   if (el) {
      this.init(el, attributes, duration, method);
   }
};

YAHOO.util.Anim.prototype = {
   /**
    * Returns the value computed by the animation's "method".
    * @param {String} attribute The name of the attribute.
    * @param {Number} start The value this attribute should start from for this animation.
    * @param {Number} end  The value this attribute should end at for this animation.
    * @return {Number} The Value to be applied to the attribute.
    */
   doMethod: function(attribute, start, end) {
      return this.method(this.currentFrame, start, end - start, this.totalFrames);
   },

   /**
    * Applies a value to an attribute
    * @param {String} attribute The name of the attribute.
    * @param {Number} val The value to be applied to the attribute.
    * @param {String} unit The unit ('px', '%', etc.) of the value.
    */
   setAttribute: function(attribute, val, unit) {
      YAHOO.util.Dom.setStyle(this.getEl(), attribute, val + unit);
   },

   /**
    * Returns current value of the attribute.
    * @param {String} attribute The name of the attribute.
    * @return {Number} val The current value of the attribute.
    */
   getAttribute: function(attribute) {
      return parseFloat( YAHOO.util.Dom.getStyle(this.getEl(), attribute));
   },

   /**
    * Per attribute units that should be used by default.
    * @type Object
    */
   defaultUnits: null,

   /**
    * The default unit to use for all attributes if not defined per attribute.
    * @type String
    */
   defaultUnit: 'px',

   /**
    * @param {HTMLElement | String} el Reference to the element that will be animated
    * @param {Object} attributes The attribute(s) to be animated.
    * Each attribute is an object with at minimum a "to" or "by" member defined.
    * Additional optional members are "from" (defaults to current value), "units" (defaults to "px").
    * All attribute names use camelCase.
    * @param {Number} duration (optional, defaults to 1 second) Length of animation (frames or seconds), defaults to time-based
    * @param {Function} method (optional, defaults to YAHOO.util.Easing.easeNone) Computes the values that are applied to the attributes per frame (generally a YAHOO.util.Easing method)
    */
   init: function(el, attributes, duration, method) {

      /**
       * Whether or not the animation is running.
       * @private
       * @type Boolean
       */
      var isAnimated = false;

      /**
       * A Date object that is created when the animation begins.
       * @private
       * @type Date
       */
      var startTime = null;

      /**
       * A Date object that is created when the animation ends.
       * @private
       * @type Date
       */
      var endTime = null;

      /**
       * The number of frames this animation was able to execute.
       * @private
       * @type Int
       */
      var actualFrames = 0;

      /**
       * The attribute values that will be used if no "from" is supplied.
       * @private
       * @type Object
       */
      var defaultValues = {};

      /**
       * The element to be animated.
       * @private
       * @type HTMLElement
       */
      el = YAHOO.util.Dom.get(el);

      /**
       * The collection of attributes to be animated.
       * Each attribute must have at least a "to" or "by" defined in order to animate.
       * If "to" is supplied, the animation will end with the attribute at that value.
       * If "by" is supplied, the animation will end at that value plus its starting value.
       * If both are supplied, "to" is used, and "by" is ignored.
       * Optional additional member include "from" (the value the attribute should start animating from, defaults to current value), and "unit" (the units to apply to the values).
       * @type Object
       */
      this.attributes = attributes || {};

      /**
       * The length of the animation.  Defaults to "1" (second).
       * @type Number
       */
      this.duration = duration || 1;

      /**
       * The method that will provide values to the attribute(s) during the animation.
       * Defaults to "YAHOO.util.Easing.easeNone".
       * @type Function
       */
      this.method = method || YAHOO.util.Easing.easeNone;

      /**
       * Whether or not the duration should be treated as seconds.
       * Defaults to true.
       * @type Boolean
       */
      this.useSeconds = true; // default to seconds

      /**
       * The location of the current animation on the timeline.
       * In time-based animations, this is used by AnimMgr to ensure the animation finishes on time.
       * @type Int
       */
      this.currentFrame = 0;

      /**
       * The total number of frames to be executed.
       * In time-based animations, this is used by AnimMgr to ensure the animation finishes on time.
       * @type Int
       */
      this.totalFrames = YAHOO.util.AnimMgr.fps;


      /**
       * Returns a reference to the animated element.
       * @return {HTMLElement}
       */
      this.getEl = function() { return el; };


      /**
       * Sets the default value to be used when "from" is not supplied.
       * @param {String} attribute The attribute being set.
       * @param {Number} val The default value to be applied to the attribute.
       */
      this.setDefault = function(attribute, val) {
         if ( isNaN(val) ) { // if 'auto' or other non-number, set defaults for well known attributes, zero for others
            switch(attribute) {
               case'width':
                  val = el.clientWidth || el.offsetWidth; // computed width
                  break;
               case 'height':
                  val = el.clientHeight || el.offsetHeight; // computed height
                  break;
               case 'left':
                  if (YAHOO.util.Dom.getStyle(el, 'position') == 'absolute') {
                     val = el.offsetLeft; // computed left
                  } else {
                     val = 0;
                  }
                  break;
               case 'top':
                  if (YAHOO.util.Dom.getStyle(el, 'position') == 'absolute') {
                     val = el.offsetTop; // computed top
                  } else {
                     val = 0;
                  }
                  break;
               default:
                  val = 0;
            }
         }

         defaultValues[attribute] = val;
      }

      /**
       * Returns the default value for the given attribute.
       * @param {String} attribute The attribute whose value will be returned.
       */
      this.getDefault = function(attribute) {
         return defaultValues[attribute];
      };

      /**
       * Checks whether the element is currently animated.
       * @return {Boolean} current value of isAnimated.
       */
      this.isAnimated = function() {
         return isAnimated;
      };

      /**
       * Returns the animation start time.
       * @return {Date} current value of startTime.
       */
      this.getStartTime = function() {
         return startTime;
      };

      /**
       * Starts the animation by registering it with the animation manager.
       */
      this.animate = function() {
         this.onBeforeStart.fire();

         this.totalFrames = ( this.useSeconds ) ? YAHOO.util.AnimMgr.fps * this.duration : this.duration;
         YAHOO.util.AnimMgr.registerElement(this);

         // get starting values or use defaults
         var attributes = this.attributes;
         var el = this.getEl();
         var val;

         for (var attribute in attributes) {
            val = this.getAttribute(attribute);
            this.setDefault(attribute, val);
         }

         isAnimated = true;
         actualFrames = 0;
         startTime = new Date();

         var data = {
            time: startTime
         };

         this.onStart.fire(data);
      };

      /**
       * Stops the animation.  Normally called by AnimMgr when animation completes.
       */
      this.stop = function() {
         this.currentFrame = 0;

         endTime = new Date();

         var data = {
            time: endTime,
            duration: endTime - startTime,
            frames: actualFrames,
            fps: actualFrames / this.duration
         };

         isAnimated = false;
         actualFrames = 0;

         this.onComplete.fire(data);
      };

      /**
       * Feeds the starting and ending values for each animated attribute to doMethod once per frame, then applies the resulting value to the attribute(s).
       * @private
       */
      var onTween = function() {
         var start;
         var end = null;
         var val;
         var unit;
         var attributes = this['attributes'];

         for (var attribute in attributes) {
            unit = attributes[attribute]['unit'] || this.defaultUnits[attribute] || this.defaultUnit;

            if (typeof attributes[attribute]['from'] != 'undefined') {
               start = attributes[attribute]['from'];
            } else {
               start = this.getDefault(attribute);
            }

            // To beats by, per SMIL 2.1 spec
            if (typeof attributes[attribute]['to'] != 'undefined') {
               end = attributes[attribute]['to'];
            } else if (typeof attributes[attribute]['by'] != 'undefined') {
               end = start + attributes[attribute]['by'];
            }

            // if end is null, dont change value
            if (end !== null && typeof end != 'undefined') {

               val = this.doMethod(attribute, start, end);

               // negative not allowed for these (others too, but these are most common)
               if ( (attribute == 'width' || attribute == 'height' || attribute == 'opacity') && val < 0 ) {
                  val = 0;
               }

               this.setAttribute(attribute, val, unit);
            }
         }

         actualFrames += 1;
      };

      /**
       * Custom event that fires after onStart, useful in subclassing
       * @private
       */
      this._onStart = new YAHOO.util.CustomEvent('_onStart', this);

      /**
       * Custom event that fires when animation begins
       * Listen via subscribe method
       */
      this.onStart = new YAHOO.util.CustomEvent('start', this);

      /**
       * Custom event that fires between each frame
       * Listen via subscribe method
       */
      this.onTween = new YAHOO.util.CustomEvent('tween', this);

      /**
       * Custom event that fires after onTween
       * @private
       */
      this._onTween = new YAHOO.util.CustomEvent('_tween', this);

      /**
       * Custom event that fires when animation ends
       * Listen via subscribe method
       */
      this.onComplete = new YAHOO.util.CustomEvent('complete', this);

      this._onTween.subscribe(onTween);
   }
};

/**
 * Custom event that fires when animation begins.
 * Listen via "subscribe" method.
 * DO NOT OVERRIDE (please)
 */
YAHOO.util.Anim.prototype.onStart = new YAHOO.util.CustomEvent('start', this);

/**
 * Custom event that fires between each frame.
 * Listen via "subscribe" method.
 * DO NOT OVERRIDE (please)
 */
YAHOO.util.Anim.prototype.onTween = new YAHOO.util.CustomEvent('tween', this);

/**
 * Custom event that fires when animation ends.
 * Listen via "subscribe" method.
 * DO NOT OVERRIDE (please)
 */
YAHOO.util.Anim.prototype.onComplete = new YAHOO.util.CustomEvent('complete', this);

/**
 * The collection of attributes to be animated.
 * <p>Example: <code>this.attributes.width = { from: 10, to: 100 }</code></p>
 * <p>Each attribute must have at least a "to" or "by" defined in order to animate.  </p>
 * <p>If "to" is supplied, the animation will end with the attribute at that value.  </p>
 * <p>If "by" is supplied, the animation will end at that value plus its starting value. </p>
 * <p>If both are supplied, "to" is used, and "by" is ignored. </p>
 * Optional additional member include "from" (the value the attribute should start animating from, defaults to current value), and "unit" (the units to apply to the values).
 * @type Object
 */
YAHOO.util.Anim.prototype.attributes = {};

/**
 * The length of the animation.  Defaults to "1" (second).
 * @type Number
 */
YAHOO.util.Anim.prototype.duration = 1;

/**
 * The method that will provide values to the attribute(s) during the animation.
 * Defaults to "YAHOO.util.Easing.easeNone".
 * @type Function
 */
YAHOO.util.Anim.prototype.method = YAHOO.util.Easing.easeNone;

/**
 * Whether or not the duration should be treated as seconds.
 * Defaults to true.
 * @type Boolean
 */
YAHOO.util.Anim.prototype.useSeconds = true; // default to seconds

/**
 * The location of the current animation on the timeline.
 * In time-based animations, this is used by AnimMgr to ensure the animation finishes on time.
 * @type Int
 */
YAHOO.util.Anim.prototype.currentFrame = 0;

/**
 * The total number of frames to be executed.
 * In time-based animations, this is used by AnimMgr to ensure the animation finishes on time.
 * @type Int
 */
YAHOO.util.Anim.prototype.totalFrames = YAHOO.util.AnimMgr.fps;


/**
 * Returns a reference to the animated element.
 * @return {HTMLElement}
 */
YAHOO.util.Anim.prototype.getEl = function() { return el; };


/**
 * Sets the default value to be used when "from" is not supplied.
 * @param {String} attribute The attribute being set.
 * @param {Number} val The default value to be applied to the attribute.
 */
YAHOO.util.Anim.prototype.setDefault = function(attribute, val) {
   if ( isNaN(val) ) { // if 'auto' or other non-number, set defaults for well known attributes, zero for others
      switch(attribute) {
         case'width':
            val = el.clientWidth || el.offsetWidth; // computed width
            break;
         case 'height':
            val = el.clientHeight || el.offsetHeight; // computed height
            break;
         case 'left':
            if (YAHOO.util.Dom.getStyle(el, 'position') == 'absolute') {
               val = el.offsetLeft; // computed left
            } else {
               val = 0;
            }
            break;
         case 'top':
            if (YAHOO.util.Dom.getStyle(el, 'position') == 'absolute') {
               val = el.offsetTop; // computed top
            } else {
               val = 0;
            }
            break;
         default:
            val = 0;
      }
   }

   defaultValues[attribute] = val;
}

/**
 * Returns the default value for the given attribute.
 * @param {String} attribute The attribute whose value will be returned.
 */
YAHOO.util.Anim.prototype.getDefault = function(attribute) {
   return defaultValues[attribute];
};

/**
 * Checks whether the element is currently animated.
 * @return {Boolean} current value of isAnimated.
 */
YAHOO.util.Anim.prototype.isAnimated = function() {
   return isAnimated;
};

/**
 * Returns the animation start time.
 * @return {Date} current value of startTime.
 */
YAHOO.util.Anim.prototype.getStartTime = function() {
   return startTime;
};

/**
 * Starts the animation by registering it with the animation manager.
 */
YAHOO.util.Anim.prototype.animate = function() {
   this.onBeforeStart.fire();

   this.totalFrames = ( this.useSeconds ) ? YAHOO.util.AnimMgr.fps * this.duration : this.duration;
   YAHOO.util.AnimMgr.registerElement(this);

   // get starting values or use defaults
   var attributes = this.attributes;
   var el = this.getEl();
   var val;

   for (var attribute in attributes) {
      val = this.getAttribute(attribute);
      this.setDefault(attribute, val);
   }

   isAnimated = true;
   actualFrames = 0;
   startTime = new Date();

   var data = {
      time: startTime
   };

   this.onStart.fire(data);
};

/**
 * Stops the animation.  Normally called by AnimMgr when animation completes.
 */
YAHOO.util.Anim.prototype.stop = function() {
   this.currentFrame = 0;

   endTime = new Date();

   var data = {
      time: endTime,
      duration: endTime - startTime,
      frames: actualFrames,
      fps: actualFrames / this.duration
   };

   isAnimated = false;
   actualFrames = 0;

   this.onComplete.fire(data);
};