Siento volver bajo el mismo tema, pero he cambiado mucho el script, así que en vez de actualizar, voy a hacer otra entrada. La demo sigue siendo la misma, pero admite muchas más cosas.

Demo

He añadido a las funciones de medida un fix para que «getComputedStyle» funcione en IE 7-8 (en teoría).

Además, he realizado un script para pasar de hexadecimal a RGB (ya que animaremos el color).


/*******************************************************
* IE FIX
*/
if (!window.getComputedStyle) {
window.getComputedStyle = function(el, pseudo) {
this.el = el;
this.getPropertyValue = function(prop) {
var re = /(\-([a-z]){1})/g;
if (prop == 'float') prop = 'styleFloat';
if (re.test(prop)) {
prop = prop.replace(re, function () {
return arguments[2].toUpperCase();
});
}
return el.currentStyle[prop] ? el.currentStyle[prop] : null;
}
return this;
}
}

/*********************************************************
* FUNCIONES DE MEDIDAS
*/
function alturaAuto(elem){
var id=document.getElementById(elem);
var alturaTotal=parseInt(id.offsetHeight);
//Padding Top
var paddingTop=parseInt(window.getComputedStyle(id).getPropertyValue("padding-top"));
//paddingBottom
var paddingBottom=parseInt(window.getComputedStyle(id).getPropertyValue("padding-bottom"));;
//borderTop
var borderTop=parseInt(window.getComputedStyle(id).getPropertyValue("border-top"));
//borderBottom
var borderBottom=parseInt(window.getComputedStyle(id).getPropertyValue("border-bottom"));;
var altura=alturaTotal-paddingTop-paddingBottom-borderTop-borderBottom;
return altura;
}

function anchuraAuto(elem){
var id=document.getElementById(elem);
var anchuratotal=id.offsetWidth;
//Padding Top
var paddingLeft=parseInt(window.getComputedStyle(id).getPropertyValue("padding-left"));
//paddingBottom
var paddingRight=parseInt(window.getComputedStyle(id).getPropertyValue("padding-right"));;
//borderTop
var borderLeft=parseInt(window.getComputedStyle(id).getPropertyValue("border-left"));
//borderBottom
var borderRight=parseInt(window.getComputedStyle(id).getPropertyValue("border-right"));;
var anchura=anchuraTotal-paddingLeft-paddingRight-borderLeft-borderRight;
return anchura;
}

function hexargb(valor){
var val=valor.substring(1);
if(val.length==3){salto=1}else{salto=2}
var hex=[val.substring(0,salto),
val.substring(salto,2*salto),
val.substring(2*salto,3*salto)];
if(val.length==3){
hex[0]=hex[0]+hex[0]
hex[1]=hex[1]+hex[1]
hex[2]=hex[2]+hex[2]
}
var rgb=[parseInt(hex[0],16),
parseInt(hex[1],16),
parseInt(hex[2],16)]
var colorrgb="rgb("+rgb[0]+","+rgb[1]+","+rgb[2]+")";
return colorrgb;
}

En cuanto a la función para animar, he pasado de usar rangos de valores (mayor y menor) a usar el loop for y el número de veces. Además, se pueden animar varias propiedades a la vez. Es decir: en vez de poner animar('ID','propiedad',valor,tiempo) usaremos animar('ID',{'propiedad1':valor1,'propiedad2':valor2},tiempo,callback). Además, se puede modificar el fondo, y encadenar efectos con el callback (ver demo)


/*********************************************************
* Animaciones
*/
/*===================================================
* Kit completo de animaciones (faltan:color,margin,padding y demás propiedades mixtas);
*/
function animar(elem,propiedades,tiempo,callback){
if(typeof(Timeout) != "undefined"){return false;}
for(var propiedad in propiedades){
if(propiedad==Object.keys(propiedades)[Object.keys(propiedades).length-1]){
animar1(elem,propiedad,propiedades[propiedad],tiempo,callback)
}else{
animar1(elem,propiedad,propiedades[propiedad],tiempo)
}
//animar1(elem,propiedad,propiedades[propiedad],tiempo)
};
}
function animar1(elem,prop,valfinal,tiempo,callback){
var id=document.getElementById(elem);
//Frames por segundo. Con 45 va perfecto, pero con 34 también va bien
var fps=45;
if(!tiempo){tiempo=600}
//Segundos
var secs=tiempo/1000;
//Veces que se repetirá la función
var veces=fps*secs;
//valor inicial
var valinicial=parseFloat(window.getComputedStyle(id).getPropertyValue(prop));

if ((prop=="top"||prop=="bottom"||prop=="left"||prop=="right")&&window.getComputedStyle(id).getPropertyValue(prop)=="auto"){valinicial=0;}

//Para evitar top y bottom a la vez y left y right a la vez
if(prop=="top"&&id.style.bottom){id.style.bottom="auto"}else if(prop=="bottom"&&id.style.top){id.style.top="auto"} else if(prop=="left"&&id.style.right){id.style.right="auto"}else if(prop=="right"&&id.style.left){id.style.left="auto"}

//si altura y anchura son auto, calculamos
if(prop=="height"&&window.getComputedStyle(id).getPropertyValue(prop)=="auto"){valinicial=alturaAuto(elem)}
if(prop=="width"&&window.getComputedStyle(id).getPropertyValue(prop)=="auto"){valinicial=anchuraAuto(elem)}

//nombre para javascript
if(prop=="font-size"){prop="fontSize"}

//se lo asignamos
if(prop=="opacity"||prop=="z-index"||prop=="zoom"){
id.style[prop]=valinicial;
}else if(prop!="color"&&prop!="background-color"){
id.style[prop]=valinicial+'px';
}
//Cambio
var cambio=valfinal-valinicial;

//pixels por vez
var ppv=cambio/veces;
if(prop!="opacity"&&prop!="fontSize"){
if(ppv<1&&ppv>0){ppv=1}else if(ppv>-1&&ppv<0){ppv=-1}
}
//tiempo por vez
var tpv=tiempo/veces;
if(veces<1){veces=1}

//Si animamos color
if (prop=="color"||prop=="background-color"){
valinicial=window.getComputedStyle(id).getPropertyValue(prop)
animarcolor(elem,prop,valinicial,valfinal,tiempo,veces);
return false;
}
//Posición para que se vea el cambio
var position=window.getComputedStyle(id).getPropertyValue("position");
if(position=="static"){id.style.position="relative"}

//Llamamos a la función para animar
animar2(elem,prop,valinicial,valfinal,veces,tpv,ppv,callback);
}
function animar2(elem,prop,valinicial,valfinal,veces,tpv,ppv,callback){
var id=document.getElementById(elem);
var i=0;
for(i=0;i<veces;i++){
(function(){
var vez=i+1;
function cambio(){
var estilo=valinicial+vez*ppv
if((valinicial==valfinal)||(valinicial>valfinal&&estilo<valfinal)||(valinicial<valfinal&&estilo>valfinal)){return false}
if(prop=="opacity"||prop=="z-index"||prop=="zoom"){
id.style[prop]=estilo
}else{

id.style[prop]=estilo+'px'
}
}
Timeout=setTimeout(cambio,vez*tpv);

})();

}
setTimeout("set1('"+elem+"','"+prop+"',"+valinicial+","+valfinal+","+callback+")",veces*tpv)
}


function animarcolor(elem,prop,valinicial,valfinal,tiempo,veces,callback){
var id=document.getElementById(elem);
var tpv=tiempo/veces;
//valores iniciales
if(valinicial.substring(0,1)=="#"){valinicial=hexargb(valinicial)}
if(prop=="background-color"){prop="backgroundColor"}
id.style[prop]=valinicial;
valinicial=[parseInt(valinicial.split(",")[0].substring(4)),parseInt(valinicial.split(",")[1]),parseInt(valinicial.split(",")[2].substring(0,valinicial.split(",")[2].length-1))]
//Quitamos el último paréntesis
//Valores finales
if(valfinal.substring(0,1)=="#"){valfinal=hexargb(valfinal)}
valfinal=[parseInt(valfinal.split(",")[0].substring(4)),parseInt(valfinal.split(",")[1]),parseInt(valfinal.split(",")[2].substring(0,valfinal.split(",")[2].length-1))];
//Cambio
var cambio=[valfinal[0]-valinicial[0], valfinal[1]-valinicial[1],valfinal[2]-valinicial[2]];

var unpv=[cambio[0]/veces,cambio[1]/veces,cambio[2]/veces]

animcolor(elem,prop,tpv,valinicial,valfinal,unpv,veces,callback)
}

function animcolor(elem,prop,tpv,valinicial,valfinal,unpv,veces,callback){
var id=document.getElementById(elem);
var coloractual=window.getComputedStyle(id).getPropertyValue(prop);
if(prop=="backgroundColor"){coloractual=window.getComputedStyle(id).getPropertyValue("background-color");}
coloractual=[parseInt(coloractual.split(",")[0].substring(4)),parseInt(coloractual.split(",")[1]),parseInt(coloractual.split(",")[2].substring(0,coloractual.split(",")[2].length-1))];
//animamos
for(var i=1;i<=veces;i++){
(function(){
var vez=i;
function cambio(){
var r,g,b;
r=valinicial[0]+vez*unpv[0];
g=valinicial[1]+vez*unpv[1];
b=valinicial[2]+vez*unpv[2];
if(r>255){r=255}if(r<0){r=0}
if(g>255){g=255}if(g<0){g=0}
if(b>255){b=255}if(b<0){b=0}
id.style[prop]="rgb("+parseInt(r)+","+parseInt(g)+","+parseInt(b)+")";
}
//el timeout se ejecuta a la vez, sólo que con distintos valores para "vez". si no, nos lo haría de golpe
Timeout=setTimeout(cambio,vez*tpv);
})();
}
setTimeout("set1('"+elem+"','"+prop+"',"+valinicial+","+valfinal+","+callback+")",veces*tpv)
}

Otra de las novedades es la función para configurar (set1). Todas las animaciones acaban en ella, y es donde se ajusta todo a los valores finales y se ejecuta el callback.


function set1(elem,prop,valinicial,valfinal,callback){
var id=document.getElementById(elem)
if(prop=="opacity"||prop=="z-index"||prop=="zoom"||prop=="color"||prop=="background-color"||prop=="backgroundColor"){
id.style[prop]=valfinal;
}else{
id.style[prop]=valfinal+'px';
}
//callback con argumentos
if (callback && typeof(callback) === "function") {
callback.apply(id,[prop,valinicial,valfinal]);
}
Timeout=undefined;

return false;
}

Finalmente, las funciones para expandir y contraer han sido modificadas, pero sólo por requerimientos de la función.


/*====================================================
* Expandir
*/

function expandir(elem,tiempo,callback){
var id=document.getElementById(elem);
if (id.style.display!="none"&&id.style.display){return false;}
id.style.visibility="hidden";
id.style.display="block";
var altura=window.getComputedStyle(id).getPropertyValue("height");
if(altura=="auto"){altura=alturaAuto(elem)}else{altura=parseInt(altura)}
id.style.height="0px";
id.style.visibility="visible";
id.style.overflow="hidden";
animar(elem,{"height":altura},tiempo,callback)
}
/*======================================================================
* Contraer
*/

function contraer(elem,tiempo,callback){
var id=document.getElementById(elem);
if (window.getComputedStyle(id).getPropertyValue("display")=="none"){return false;}
id.style.overflow="hidden";
var altura=window.getComputedStyle(id).getPropertyValue("height");
if(altura=="auto"){altura=alturaAuto(elem)}else{altura=parseInt(altura)}
id.style.height=altura+'px';
animar(elem,{"height":0},tiempo,function(){
this.style.display="none";
this.style.height=arguments[1]+'px';
//Recordad que "arguments[1]" era la altura con la que empezó
});
}
/*======================================================================
* Expandir/Contraer
*/
function expandirContraer(elem,tiempo,callback){
var id=document.getElementById(elem);
if(typeof(Timeout) != "undefined"){return false;}
if(window.getComputedStyle(id).getPropertyValue("display")=="none"){
expandir(elem,tiempo,callback);
}else{
contraer(elem,tiempo,callback);
}
}
/*======================================================================
* Fade in-out
*/
function fadeIn(elem,tiempo,callback){
var id=document.getElementById(elem);
if (window.getComputedStyle(id).getPropertyValue("display")!="none"){return false;}
id.style.opacity=0;
id.style.display="block";
animar(elem,{"opacity":1},tiempo,callback)
}
function fadeOut(elem,tiempo,callback){
var id=document.getElementById(elem);
if (window.getComputedStyle(id).getPropertyValue("display")=="none"){return false;};
if(callback&&typeof(callback)==="function"){
callback1=function(){
this.style.display="none";
callback();
}
}else{
callback1=callback1=function(){
this.style.display="none";
}
}
animar(elem,{"opacity":0},tiempo,callback1);
//this.style.opacity=arguments[0];
}
function fadeInOut(elem,tiempo,callback){
var id=document.getElementById(elem);
if(typeof(Timeout) != "undefined"){return false;}

if (window.getComputedStyle(id).getPropertyValue("display")!="none"){
fadeOut(elem,tiempo,callback);
}else{
fadeIn(elem,tiempo,callback)
}
}

Ahora os dejo con la demo otra vez. Divertíos

Demo

Sigo recomendando el blog #Web{border:none} , de donde he sacado la idea del loop (aunque allí solo lo aplica al color). La idea de los objetos para animar la saqué del mismo jQuery