cssa.js

Summary

This file is to be used for animating CSS properties with a few lines of code. Web developer then only needs to define transitions of numbers or values in a given duration by looping/reverting or not. Check CSS-Anim for more information

Version: 0.1

Author: Arnaud Aubert - aaubert@magesi.com


Class Summary
cssa_Animation Abstract class that defines the concept of an animation.
cssa_AnimationColor Animation class to change CSS properties of any color typed value
cssa_AnimationInteger Animation class to change CSS properties of the integer type of any unit
cssa_AnimationTree This is the object managing all the animations of a page.
RGBColor  

Method Summary
static void cssa_init_framework()
           This function must be called once per page needing animation It will use set the timer depending on cssa_keyframe_length
static void cssa_timerfunc()
           This function is called every cssa_keyframe_length milliseconds to compute new values for CSS properties

/** 
 * @fileoverview This file is to be used for animating CSS properties
 * with a few lines of code.
 * Web developer then only needs to define transitions of numbers or 
 * values in a given duration by looping/reverting or not.
 * Check {@link http://sourceforge.net/projects/css-anim CSS-Anim} for
 * more information
 *
 * @author Arnaud Aubert - aaubert@magesi.com
 * @version 0.1 
 */



/**
 * The number of milliseconds between each recomputation of the CSS properties
 * @type int
 */
var cssa_keyframe_length = 20;

/**
 * The list of all the animations for the page
 * @type cssa_AnimationTree
 */
var cssa_Anims = new cssa_AnimationTree();

/**
 * This function is called every cssa_keyframe_length milliseconds to compute 
 * new values for CSS properties
 * @author Arnaud Aubert - aaubert@magesi.com
 */
function cssa_timerfunc()
{
  // Enumerate all elements to animate
  var curAnim;
  //for (keyAnim in cssa_Anims.animations)
  for (keyAnim = 0 ; keyAnim < cssa_Anims.animations.length; keyAnim++)
  {
    curAnim = cssa_Anims.animations[keyAnim];
    
    // Animate the current animation
    curAnim.tick();
  }
}

/**
 * This function must be called once per page needing animation
 * It will use set the timer depending on cssa_keyframe_length
 * @author Arnaud Aubert - aaubert@magesi.com
 */
function cssa_init_framework()
{
  window.setInterval(cssa_timerfunc, cssa_keyframe_length);
}

/**
 * Construct a new cssa_AnimationTree object.
 * @class This is the object managing all the animations of a page.
 * Only one instance of it should exist on a single page. It holds
 * the array of animations that should run and provides functions
 * to delete them
 * @constructor
 * @return A new animation tree
 * @author Arnaud Aubert - aaubert@magesi.com
 */
function cssa_AnimationTree()
{
    /**
     * The array of the animations in the tree
     * @type Array
     */
    this.animations = new Array();
    
    /** 
    * This method deletes all animations of a given element reference
    * @param {element} oElem The element whose animations must be deleted
    * @author Arnaud Aubert - aaubert@magesi.com
    */
    this.deleteAnimationsForElem = function(oElem) {
        for (keyAnim = 0; keyAnim < cssa_Anims.animations.length; keyAnim++)
        {
            curAnim = cssa_Anims.animations[keyAnim];
            if (curAnim.elem == oElem)
            {
                // Reset the animation to its original value
                cssa_Anims.animations.splice(keyAnim, 1);
                keyAnim = -1;
            }
        }
        
    }

    /** 
    * This method can be used to delete a single given animation object
    * @param {csaa_Animation} oAnim The animation to delete
    * @author Arnaud Aubert - aaubert@magesi.com
    */
    this.deleteAnim = function(oAnim) {
        for (keyAnim = 0; keyAnim < cssa_Anims.animations.length; keyAnim++)
        {
            if (curAnim == oAnim)
            {
                // Reset the animation to its original value
                cssa_Anims.animations.splice(keyAnim, 1);
                break;
            }
        }
    }
    
    /** 
    * This method can be used to delete all existing animations from the tree
    * @author Arnaud Aubert - aaubert@magesi.com
    */
    this.resetAll = function() {
        cssa_Anims.animations.splice(0, cssa_Anims.animations.length);
    }
        
}

/**
 * Define the concept of animation
 * @class Abstract class that defines the concept of an animation. All 
 * animation classes should inherit from this one and call init() in 
 * their constructor
 * @constructor
 * @return A new animation
 * @author Arnaud Aubert - aaubert@magesi.com
 */
function cssa_Animation() {
  /**
   * The number of milliseconds the animation takes to run
   * @type int
   */
  this.duration = 0;

  /**
   * The current iteration number to complete animation
   * @type int
   */
  this.curKey = 0;
  
   /**
   * The total number of iterations to complete the animation
   * @type int
   */
  this.nbFrames = 0;
}

/** 
* This method is used internally and called by children to initialize
* basic properties.
* @param {element} elem A reference to the element being animated
* @param {String} propname Name of the CSS property name to animate
* @param {String} to Final value for the CSS property
* @param {int} duration Number of milliseconds for the animation to complete
* @param {boolean} bLoop True if the animation must restart upon completion
* @param {boolean} bRevert True if the animation should reanimate to its initial value upon completion
* @author Arnaud Aubert - aaubert@magesi.com
*/
cssa_Animation.prototype.init = function(elem, propname, to, duration, bLoop, bRevert) {
  // If the element does not have a style property, create one
  /**
   * The element being animated
   * @type element
   */
  this.elem = elem;
  
  /**
   * The CSS property being animated
   * @type String
   */
  this.propname = propname;

  /**
   * The final value for the CSS property (at the end of the animation)
   * @type String
   */
  this.to = to;
  
  /**
   * Should the animation restart upon completion
   * @type boolean
   */
  this.bLoop = bLoop;

  /**
   * Should the animation animate back to its original value
   * @type boolean
   */
  this.bRevert = bRevert;

  // Check if an animation already exist before adding it 
  for (keyAnim in cssa_Anims.animations)
  {
      curAnim = cssa_Anims.animations[keyAnim];
      if (curAnim.elem == elem && curAnim.propname == propname)
        return false;
  }
  
  return true;  
}


cssa_AnimationInteger.prototype = new cssa_Animation(); // Inheritance
/**
* Animation of any integer based value. The value can be in any unit (%, em, pt, px...)
* @class Animation class to change CSS properties of the integer type of any unit
* @constructor
* @param {element} elem A reference to the element being animated
* @param {String} propname Name of the CSS property name to animate
* @param {String} to Final value for the CSS property
* @param {int} duration Number of milliseconds for the animation to complete
* @param {boolean} bLoop True if the animation must restart upon completion
* @param {boolean} bRevert True if the animation should reanimate to its initial value upon completion
* @return A new animation of integer values
* @author Arnaud Aubert - aaubert@magesi.com
*/
function cssa_AnimationInteger(elem, propName, to, duration, bLoop, bRevert)
{
  // Initialize basic elements
  if (!cssa_Animation.prototype.init.call(this, elem, propName, to, duration, bLoop, bRevert))
  {
    cssa_Anims.deleteAnim(this);
    return null;
  }
  
  if (elem.style[propName] == "") elem.style[propName] = "0";

  /**
   * Current value of the animated integer
   * @type int
   */
  this.curVal  = parseInt( elem.style[propName] );           // Numerical value of the property
  
  /**
   * Unit of the animated integer (px, %, em, pt...)
   * @type string
   */
  this.curUnit = elem.style[propName].replace(/[0-9]*/i , "");

  /**
   * Starting integer animated value
   * @type int
   */
  this.from = parseInt( elem.style[propName] );

  // Compute the number of keyframes to do the total animation
  this.nbFrames = duration / cssa_keyframe_length;

  // Compute the animation's amplitude
  to = parseInt(this.to); from = parseInt(elem.style[propName]);
  if (to > from) iTotalDelta = to - from; else iTotalDelta = from - to;

  /**
   * Incremental value added to the property each time it is being recomputed
   * @type int
   */
  this.iInc = iTotalDelta / this.nbFrames;
  if (to < from) this.iInc *= -1;

  this.tick = function() {
    // Is the animation completed ?
    if (this.curKey >= this.nbFrames) 
    {
        // Should we loop ?
        if (this.bLoop)
        {
            if (this.bRevert)
            {
                this.iInc *= -1;
            } 
            else 
            {
                this.elem.style[propName] = this.from + this.curUnit;
                this.curVal = this.from;
            }
            
            this.curKey = 0;
        } 
        return;
    }
    else 
        this.curKey++; 
    
    // Increment the value
    this.curVal += this.iInc;
    
    // Set the new style
    this.elem.style[this.propname] = Math.round(this.curVal) + this.curUnit;
       
  }

  cssa_Anims.animations.push(this);
}


cssa_AnimationColor.prototype = new cssa_Animation();   // Inheritance
/**
* Animation of any color based value. The value can be in rgb or hex format
* @class Animation class to change CSS properties of any color typed value
* @param {element} elem A reference to the element being animated
* @param {String} propname Name of the CSS property name to animate
* @param {Color} to Final color for the CSS property
* @param {int} duration Number of milliseconds for the animation to complete
* @param {boolean} bLoop True if the animation must restart upon completion
* @param {boolean} bRevert True if the animation should reanimate to its initial value upon completion
* @return A new animation of color values
* @author Arnaud Aubert - aaubert@magesi.com
*/
function cssa_AnimationColor(elem, propName, to, duration, bLoop, bRevert)
{
  // Initialize basic elements
  if (!cssa_Animation.prototype.init.call(this, elem, propName, to, duration, bLoop, bRevert))
  {
    cssa_Anims.deleteAnim(this);
    return null;
  }
  
  // Compute the number of keyframes to do the total animation
  this.nbFrames = duration / cssa_keyframe_length;

  // Compute the animation's amplitude for red, green & blue
  if (elem.style[propName] == "") elem.style[propName] = "#000000";
  
  // Switch the rgb color to #xxxxxx format
  /**
   * Starting animated value
   * @type RGBColor
   */
  this.from = new RGBColor(elem.style[propName]);
  
  /**
   * Final value at the end of the animation
   * @type RGBColor
   */
  this.to = new RGBColor(to);

  /**
   * Current red value (0 to 255) of the animated property
   * @type int
   */
  this.curRed = this.from.r;

  /**
   * Current green value (0 to 255) of the animated property
   * @type int
   */
  this.curGreen = this.from.g;

  /**
   * Current blue value (0 to 255) of the animated property
   * @type int
   */
  this.curBlue = this.from.b;
  
  // Compute the increment values for r, g & b
  if (this.to.r > this.from.r) this.iIncRed = (this.to.r - this.from.r) / this.nbFrames; else this.iIncRed = -1 * (this.from.r - this.to.r) / this.nbFrames;
  if (this.to.g > this.from.g) this.iIncGreen = (this.to.g - this.from.g) / this.nbFrames; else this.iIncGreen = -1 * (this.from.g - this.to.g) / this.nbFrames;
  if (this.to.b > this.from.b) this.iIncBlue = (this.to.b - this.from.b) / this.nbFrames; else this.iIncBlue = -1 * (this.from.b - this.to.b) / this.nbFrames;

  this.tick = function() {
    // Is the animation completed ?
    if (this.curKey >= this.nbFrames) 
    {
        // Should we loop ?
        if (this.bLoop)
        {
            this.curKey = 0;

            if (this.bRevert)
            {
                bak = this.from; this.from = this.to; to = this.to = bak;
                this.iIncRed *= -1; this.iIncBlue *= -1; this.iIncGreen *= -1;
                return;
            }

            this.elem.style[this.propname] = this.from;
            fromrgb = this.from;
            this.curRed = fromrgb.r;
            this.curGreen = fromrgb.g;
            this.curBlue = fromrgb.b;
            return;
        } 
        else return;  // Animation completed
    }
    else 
        this.curKey++; 
    
    // Increment the value
    this.curRed += this.iIncRed;
    this.curGreen += this.iIncGreen;
    this.curBlue += this.iIncBlue;
    
    // Compute new color name
    sColor = "#";
    if (Math.round(this.curRed) < 16) sColor += "0"; sColor += Math.round(this.curRed).toString(16);
    if (Math.round(this.curGreen) < 16) sColor += "0"; sColor += Math.round(this.curGreen).toString(16);
    if (Math.round(this.curBlue) < 16) sColor += "0"; sColor += Math.round(this.curBlue).toString(16);
    
    // Set the new style
    this.elem.style[this.propname] = sColor;
       
  }

  cssa_Anims.animations.push(this);
}

/**
 * A class to parse color values
 * @author Stoyan Stefanov <sstoo@gmail.com>
 * @link   http://www.phpied.com/rgb-color-parser-in-javascript/
 * @license Use it if you like it
 */
function RGBColor(color_string)
{
    this.ok = false;

    // strip any leading #
    if (color_string.charAt(0) == '#') { // remove # if any
        color_string = color_string.substr(1,6);
    }

    color_string = color_string.replace(/ /g,'');
    color_string = color_string.toLowerCase();

    // before getting into regexps, try simple matches
    // and overwrite the input
    var simple_colors = {
        aliceblue: 'f0f8ff',
        antiquewhite: 'faebd7',
        aqua: '00ffff',
        aquamarine: '7fffd4',
        azure: 'f0ffff',
        beige: 'f5f5dc',
        bisque: 'ffe4c4',
        black: '000000',
        blanchedalmond: 'ffebcd',
        blue: '0000ff',
        blueviolet: '8a2be2',
        brown: 'a52a2a',
        burlywood: 'deb887',
        cadetblue: '5f9ea0',
        chartreuse: '7fff00',
        chocolate: 'd2691e',
        coral: 'ff7f50',
        cornflowerblue: '6495ed',
        cornsilk: 'fff8dc',
        crimson: 'dc143c',
        cyan: '00ffff',
        darkblue: '00008b',
        darkcyan: '008b8b',
        darkgoldenrod: 'b8860b',
        darkgray: 'a9a9a9',
        darkgreen: '006400',
        darkkhaki: 'bdb76b',
        darkmagenta: '8b008b',
        darkolivegreen: '556b2f',
        darkorange: 'ff8c00',
        darkorchid: '9932cc',
        darkred: '8b0000',
        darksalmon: 'e9967a',
        darkseagreen: '8fbc8f',
        darkslateblue: '483d8b',
        darkslategray: '2f4f4f',
        darkturquoise: '00ced1',
        darkviolet: '9400d3',
        deeppink: 'ff1493',
        deepskyblue: '00bfff',
        dimgray: '696969',
        dodgerblue: '1e90ff',
        feldspar: 'd19275',
        firebrick: 'b22222',
        floralwhite: 'fffaf0',
        forestgreen: '228b22',
        fuchsia: 'ff00ff',
        gainsboro: 'dcdcdc',
        ghostwhite: 'f8f8ff',
        gold: 'ffd700',
        goldenrod: 'daa520',
        gray: '808080',
        green: '008000',
        greenyellow: 'adff2f',
        honeydew: 'f0fff0',
        hotpink: 'ff69b4',
        indianred : 'cd5c5c',
        indigo : '4b0082',
        ivory: 'fffff0',
        khaki: 'f0e68c',
        lavender: 'e6e6fa',
        lavenderblush: 'fff0f5',
        lawngreen: '7cfc00',
        lemonchiffon: 'fffacd',
        lightblue: 'add8e6',
        lightcoral: 'f08080',
        lightcyan: 'e0ffff',
        lightgoldenrodyellow: 'fafad2',
        lightgrey: 'd3d3d3',
        lightgreen: '90ee90',
        lightpink: 'ffb6c1',
        lightsalmon: 'ffa07a',
        lightseagreen: '20b2aa',
        lightskyblue: '87cefa',
        lightslateblue: '8470ff',
        lightslategray: '778899',
        lightsteelblue: 'b0c4de',
        lightyellow: 'ffffe0',
        lime: '00ff00',
        limegreen: '32cd32',
        linen: 'faf0e6',
        magenta: 'ff00ff',
        maroon: '800000',
        mediumaquamarine: '66cdaa',
        mediumblue: '0000cd',
        mediumorchid: 'ba55d3',
        mediumpurple: '9370d8',
        mediumseagreen: '3cb371',
        mediumslateblue: '7b68ee',
        mediumspringgreen: '00fa9a',
        mediumturquoise: '48d1cc',
        mediumvioletred: 'c71585',
        midnightblue: '191970',
        mintcream: 'f5fffa',
        mistyrose: 'ffe4e1',
        moccasin: 'ffe4b5',
        navajowhite: 'ffdead',
        navy: '000080',
        oldlace: 'fdf5e6',
        olive: '808000',
        olivedrab: '6b8e23',
        orange: 'ffa500',
        orangered: 'ff4500',
        orchid: 'da70d6',
        palegoldenrod: 'eee8aa',
        palegreen: '98fb98',
        paleturquoise: 'afeeee',
        palevioletred: 'd87093',
        papayawhip: 'ffefd5',
        peachpuff: 'ffdab9',
        peru: 'cd853f',
        pink: 'ffc0cb',
        plum: 'dda0dd',
        powderblue: 'b0e0e6',
        purple: '800080',
        red: 'ff0000',
        rosybrown: 'bc8f8f',
        royalblue: '4169e1',
        saddlebrown: '8b4513',
        salmon: 'fa8072',
        sandybrown: 'f4a460',
        seagreen: '2e8b57',
        seashell: 'fff5ee',
        sienna: 'a0522d',
        silver: 'c0c0c0',
        skyblue: '87ceeb',
        slateblue: '6a5acd',
        slategray: '708090',
        snow: 'fffafa',
        springgreen: '00ff7f',
        steelblue: '4682b4',
        tan: 'd2b48c',
        teal: '008080',
        thistle: 'd8bfd8',
        tomato: 'ff6347',
        turquoise: '40e0d0',
        violet: 'ee82ee',
        violetred: 'd02090',
        wheat: 'f5deb3',
        white: 'ffffff',
        whitesmoke: 'f5f5f5',
        yellow: 'ffff00',
        yellowgreen: '9acd32'
    };
    for (var key in simple_colors) {
        if (color_string == key) {
            color_string = simple_colors[key];
        }
    }
    // emd of simple type-in colors

    // array of color definition objects
    var color_defs = [
        {
            re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
            example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
            process: function (bits){
                return [
                    parseInt(bits[1]),
                    parseInt(bits[2]),
                    parseInt(bits[3])
                ];
            }
        },
        {
            re: /^(\w{2})(\w{2})(\w{2})$/,
            example: ['#00ff00', '336699'],
            process: function (bits){
                return [
                    parseInt(bits[1], 16),
                    parseInt(bits[2], 16),
                    parseInt(bits[3], 16)
                ];
            }
        },
        {
            re: /^(\w{1})(\w{1})(\w{1})$/,
            example: ['#fb0', 'f0f'],
            process: function (bits){
                return [
                    parseInt(bits[1] + bits[1], 16),
                    parseInt(bits[2] + bits[2], 16),
                    parseInt(bits[3] + bits[3], 16)
                ];
            }
        }
    ];

    // search through the definitions to find a match
    for (var i = 0; i < color_defs.length; i++) {
        var re = color_defs[i].re;
        var processor = color_defs[i].process;
        var bits = re.exec(color_string);
        if (bits) {
            channels = processor(bits);
            this.r = channels[0];
            this.g = channels[1];
            this.b = channels[2];
            this.ok = true;
        }

    }

    // validate/cleanup values
    this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);
    this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);
    this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);

    // some getters
    this.toRGB = function () {
        return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';
    }
    this.toHex = function () {
        var r = this.r.toString(16);
        var g = this.g.toString(16);
        var b = this.b.toString(16);
        if (r.length == 1) r = '0' + r;
        if (g.length == 1) g = '0' + g;
        if (b.length == 1) b = '0' + b;
        return '#' + r + g + b;
    }

    // help
    this.getHelpXML = function () {

        var examples = new Array();
        // add regexps
        for (var i = 0; i < color_defs.length; i++) {
            var example = color_defs[i].example;
            for (var j = 0; j < example.length; j++) {
                examples[examples.length] = example[j];
            }
        }
        // add type-in colors
        for (var sc in simple_colors) {
            examples[examples.length] = sc;
        }

        var xml = document.createElement('ul');
        xml.setAttribute('id', 'rgbcolor-examples');
        for (var i = 0; i < examples.length; i++) {
            try {
                var list_item = document.createElement('li');
                var list_color = new RGBColor(examples[i]);
                var example_div = document.createElement('div');
                example_div.style.cssText =
                        'margin: 3px; '
                        + 'border: 1px solid black; '
                        + 'background:' + list_color.toHex() + '; '
                        + 'color:' + list_color.toHex()
                ;
                example_div.appendChild(document.createTextNode('test'));
                var list_item_value = document.createTextNode(
                    ' ' + examples[i] + ' -> ' + list_color.toRGB() + ' -> ' + list_color.toHex()
                );
                list_item.appendChild(example_div);
                list_item.appendChild(list_item_value);
                xml.appendChild(list_item);

            } catch(e){}
        }
        return xml;

    }

}


Documentation generated by JSDoc on Fri Oct 13 23:00:17 2006