# Yahoo! UI Library

### animation

Yahoo! UI Library > animation > Anim.js (source view)

    /*
    Copyright (c) 2006, Yahoo! Inc. All rights reserved.
    Code licensed under the BSD License:
    http://developer.yahoo.net/yui/license.txt
    */

    /**
     * The animation module provides allows effects to be added to HTMLElements.
     * @module animation
     */

    /**
     *
     * Base animation class that provides the interface for building animated effects.
     * <p>Usage: var myAnim = new YAHOO.util.Anim(el, { width: { from: 10, to: 100 } }, 1, YAHOO.util.Easing.easeOut);</p>
     * @class Anim
     * @namespace YAHOO.util
     * @requires YAHOO.util.AnimMgr
     * @requires YAHOO.util.Easing
     * @requires YAHOO.util.Dom
     * @requires YAHOO.util.Event
     * @requires YAHOO.util.CustomEvent
     * @constructor
     * @param {String | HTMLElement} 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)
     */

    YAHOO.util.Anim = function(el, attributes, duration, method) {
       if (el) {
          this.init(el, attributes, duration, method);
       }
    };

    YAHOO.util.Anim.prototype = {
       /**
        * Provides a readable name for the Anim instance.
        * @method toString
        * @return {String}
        */
       toString: function() {
          var el = this.getEl();
          var id = el.id || el.tagName;
          return ("Anim " + id);
       },

       patterns: { // cached for performance
          noNegatives:      /width|height|opacity|padding/i, // keep at zero or above
          offsetAttribute:  /^((width|height)|(top|left))$/, // use offsetValue as default
          defaultUnit:      /width|height|top$|bottom$|left$|right$/i, // use 'px' by default
          offsetUnit:       /\d+(em|%|en|ex|pt|in|cm|mm|pc)$/i // IE may return these, so convert these to offset
       },

       /**
        * Returns the value computed by the animation's "method".
        * @method doMethod
        * @param {String} attr 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(attr, start, end) {
          return this.method(this.currentFrame, start, end - start, this.totalFrames);
       },

       /**
        * Applies a value to an attribute.
        * @method setAttribute
        * @param {String} attr 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(attr, val, unit) {
          if ( this.patterns.noNegatives.test(attr) ) {
             val = (val > 0) ? val : 0;
          }

          YAHOO.util.Dom.setStyle(this.getEl(), attr, val + unit);
       },

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

          if (val !== 'auto' && !this.patterns.offsetUnit.test(val)) {
             return parseFloat(val);
          }

          var a = this.patterns.offsetAttribute.exec(attr) || [];
          var pos = !!( a[3] ); // top or left
          var box = !!( a[2] ); // width or height

          // use offsets for width/height and abs pos top/left
          if ( box || (YAHOO.util.Dom.getStyle(el, 'position') == 'absolute' && pos) ) {
             val = el['offset' + a[0].charAt(0).toUpperCase() + a[0].substr(1)];
          } else { // default to zero for other 'auto'
             val = 0;
          }

          return val;
       },

       /**
        * Returns the unit to use when none is supplied.
        * @method getDefaultUnit
        * @param {attr} attr The name of the attribute.
        * @return {String} The default unit to be used.
        */
       getDefaultUnit: function(attr) {
           if ( this.patterns.defaultUnit.test(attr) ) {
             return 'px';
           }

           return '';
       },

       /**
        * Sets the actual values to be used during the animation.
        * @method setRuntimeAttribute
        * Should only be needed for subclass use.
        * @param {Object} attr The attribute object
        * @private
        */
       setRuntimeAttribute: function(attr) {
          var start;
          var end;
          var attributes = this.attributes;

          this.runtimeAttributes[attr] = {};

          var isset = function(prop) {
             return (typeof prop !== 'undefined');
          };

          if ( !isset(attributes[attr]['to']) && !isset(attributes[attr]['by']) ) {
             return false; // note return; nothing to animate to
          }

          start = ( isset(attributes[attr]['from']) ) ? attributes[attr]['from'] : this.getAttribute(attr);

          // To beats by, per SMIL 2.1 spec
          if ( isset(attributes[attr]['to']) ) {
             end = attributes[attr]['to'];
          } else if ( isset(attributes[attr]['by']) ) {
             if (start.constructor == Array) {
                end = [];
                for (var i = 0, len = start.length; i < len; ++i) {
                   end[i] = start[i] + attributes[attr]['by'][i];
                }
             } else {
                end = start + attributes[attr]['by'];
             }
          }

          this.runtimeAttributes[attr].start = start;
          this.runtimeAttributes[attr].end = end;

          // set units if needed
          this.runtimeAttributes[attr].unit = ( isset(attributes[attr].unit) ) ? attributes[attr]['unit'] : this.getDefaultUnit(attr);
       },

       /**
        * Constructor for Anim instance.
        * @method init
        * @param {String | HTMLElement} 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.
           * @property isAnimated
           * @private
           * @type Boolean
           */
          var isAnimated = false;

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

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

          /**
           * The element to be animated.
           * @property el
           * @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).
           * @property attributes
           * @type Object
           */
          this.attributes = attributes || {};

          /**
           * The length of the animation.  Defaults to "1" (second).
           * @property duration
           * @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".
           * @property method
           * @type Function
           */
          this.method = method || YAHOO.util.Easing.easeNone;

          /**
           * Whether or not the duration should be treated as seconds.
           * Defaults to true.
           * @property useSeconds
           * @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.
           * @property currentFrame
           * @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.
           * @property totalFrames
           * @type Int
           */
          this.totalFrames = YAHOO.util.AnimMgr.fps;

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

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

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

          this.runtimeAttributes = {};

          var logger = {};
          logger.log = function() {YAHOO.log.apply(window, arguments)};

          logger.log('creating new instance of ' + this);

          /**
           * Starts the animation by registering it with the animation manager.
           * @method animate
           */
          this.animate = function() {
             if ( this.isAnimated() ) { return false; }

             this.currentFrame = 0;

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

             YAHOO.util.AnimMgr.registerElement(this);
          };

          /**
           * Stops the animation.  Normally called by AnimMgr when animation completes.
           * @method stop
           * @param {Boolean} finish (optional) If true, animation will jump to final frame.
           */
          this.stop = function(finish) {
             if (finish) {
                 this.currentFrame = this.totalFrames;
                 this._onTween.fire();
             }
             YAHOO.util.AnimMgr.stop(this);
          };

          var onStart = function() {
             this.onStart.fire();

             this.runtimeAttributes = {};
             for (var attr in this.attributes) {
                this.setRuntimeAttribute(attr);
             }

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

          /**
           * 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 data = {
                duration: new Date() - this.getStartTime(),
                currentFrame: this.currentFrame
             };

             data.toString = function() {
                return (
                   'duration: ' + data.duration +
                   ', currentFrame: ' + data.currentFrame
                );
             };

             this.onTween.fire(data);

             var runtimeAttributes = this.runtimeAttributes;

             for (var attr in runtimeAttributes) {
                this.setAttribute(attr, this.doMethod(attr, runtimeAttributes[attr].start, runtimeAttributes[attr].end), runtimeAttributes[attr].unit);
             }

             actualFrames += 1;
          };

          var onComplete = function() {
             var actual_duration = (new Date() - startTime) / 1000 ;

             var data = {
                duration: actual_duration,
                frames: actualFrames,
                fps: actualFrames / actual_duration
             };

             data.toString = function() {
                return (
                   'duration: ' + data.duration +
                   ', frames: ' + data.frames +
                   ', fps: ' + data.fps
                );
             };

             isAnimated = false;
             actualFrames = 0;
             this.onComplete.fire(data);
          };

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

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

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

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

          /**
           * Custom event that fires when animation ends
           * Listen via subscribe method (e.g. myAnim.onComplete.subscribe(someFunction)
           * @event onComplete
           */
          this.onComplete = new YAHOO.util.CustomEvent('complete', this);
          /**
           * Custom event that fires after onComplete
           * @private
           */
          this._onComplete = new YAHOO.util.CustomEvent('_complete', this, true);

          this._onStart.subscribe(onStart);
          this._onTween.subscribe(onTween);
          this._onComplete.subscribe(onComplete);
       }
    };

Copyright © 2006 Yahoo! Inc. All rights reserved.