# Yahoo! UI Library

### animation 2.7.0

Yahoo! UI Library > animation > Anim.js (source view)
Search:
Filters Show Private Show Protected Show Deprecated

    (function() {

    var Y = YAHOO.util;

    /*
    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
     * @requires yahoo, event, dom
     */

    /**
     *
     * 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)
     */

    var Anim = function(el, attributes, duration, method) {
        if (!el) {
            YAHOO.log('element required to create Anim instance', 'error', 'Anim');
        }
        this.init(el, attributes, duration, method);
    };

    Anim.NAME = 'Anim';

    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 (this.constructor.NAME + ': ' + 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) {
            var el = this.getEl();
            if ( this.patterns.noNegatives.test(attr) ) {
                val = (val > 0) ? val : 0;
            }

            if ('style' in el) {
                Y.Dom.setStyle(el, attr, val + unit);
            } else if (attr in el) {
                el[attr] = val;
            }
        },

        /**
         * 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 = Y.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

            if ('style' in el) {
                // use offsets for width/height and abs pos top/left
                if ( box || (Y.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;
                }
            } else if (attr in el) {
                val = el[attr];
            }

            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.  Should only be needed for subclass use.
         * @method setRuntimeAttribute
         * @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] * 1; // times 1 to cast "by"
                    }
                } else {
                    end = start + attributes[attr]['by'] * 1;
                }
            }

            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);
            return true;
        },

        /**
         * 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 = Y.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 = !YAHOO.lang.isUndefined(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 || Y.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 = Y.AnimMgr.fps;

            /**
             * Changes the animated element
             * @method setEl
             */
            this.setEl = function(element) {
                el = Y.Dom.get(element);
            };

            /**
             * 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(Y.AnimMgr.fps * this.duration) : this.duration;

                if (this.duration === 0 && this.useSeconds) { // jump to last frame if zero second duration
                    this.totalFrames = 1;
                }
                Y.AnimMgr.registerElement(this);
                return true;
            };

            /**
             * 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 (!this.isAnimated()) { // nothing to stop
                    return false;
                }

                if (finish) {
                     this.currentFrame = this.totalFrames;
                     this._onTween.fire();
                }
                Y.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 Y.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 Y.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 Y.CustomEvent('tween', this);

            /**
             * Custom event that fires after onTween
             * @private
             */
            this._onTween = new Y.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 Y.CustomEvent('complete', this);
            /**
             * Custom event that fires after onComplete
             * @private
             */
            this._onComplete = new Y.CustomEvent('_complete', this, true);

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

        Y.Anim = Anim;
    })();

Copyright © 2009 Yahoo! Inc. All rights reserved.