Si bien el otro día os hablaba de unas animaciones caseras en javascript que había creado, con en inconveniente de que eran muy lentas (pero mucho), ahora vengo a publicar una versión mucho mejor, ya que no carga casi nada al navegador y, además, la calidad se puede modificar cambiando sólo una cifra.

Bueno, iré paso a paso. Empezaremos por las funciones de medida. Son dos funciones para hallar el ancho y el alto (cuando está, por ejemplo, en «auto»). No tienen demasiada chicha.


/*********************************************************
* FUNCIONES DE MEDIDAS
*/
function alturaAuto(elem){
var id=document.getElementById(elem);
var alturaTotal=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;
//paddingLeft
var paddingLeft=parseInt(window.getComputedStyle(id).getPropertyValue("padding-left"));
//paddingRight
var paddingRight=parseInt(window.getComputedStyle(id).getPropertyValue("padding-right"));;
//borderLeft
var borderLeft=parseInt(window.getComputedStyle(id).getPropertyValue("border-left"));
//borderRight
var borderRight=parseInt(window.getComputedStyle(id).getPropertyValue("border-right"));;
var anchura=anchuraTotal-paddingLeft-paddingRight-borderLeft-borderRight;
return anchura;
}

Ahora llega lo interesante: la función para animar. La calidad se puede cambiar aumentando o disminuyendo la variable fps. He incluido la opción de callback,para usarla más abajo. Tened en cue


/*********************************************************
* Animaciones
*/
/*===================================================
* Kit completo de animaciones (faltan:color,margin,padding y demás propiedades mixtas);
*/
function animar(elem,prop,valfinal,tiempo,callback){//elemento, propiedead para animar, valor final, tiempo que se va a tardar (por defecto 400) y función de 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=400}//tiempo por defecto
//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;}//Ponemos el valor a cero si se posiciona automáticamente

//Para evitar top y bottom a la vez y left y right a la vez
if(prop=="top"){id.style.bottom="auto"}else if(prop=="bottom"){id.style.top="auto"} else if(prop=="left"){id.style.right="auto"}else if(prop=="right"){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)}

//Asignamos ese valor (para facilitar después las cosas)
if(prop=="opacity"||prop=="z-index"||prop=="zoom"){
id.style[prop]=valinicial;
}else{
id.style[prop]=valinicial+'px';
}

//Cambio (valor absoluto)
var cambio=valfinal-valinicial;
if(cambio<0){cambio=-cambio}

//Pixels por vez (lo que se moverá cada vez que llamemos a la función)
var ppv=cambio/veces;
if(prop!="opacity"){
if(ppv<1){ppv=1}
}

//El tiempo tras el que se llamará la función vez tras vez

var tpv=tiempo/veces;
if(veces<1){veces=1}

//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,tpv,ppv,callback);
}

function animar2(elem,prop,valinicial,valfinal,tpv,ppv,callback){//guardamos el valor inicial para usarlo como argumento en el callback
var id=document.getElementById(elem);

//Animamos
//Lo de "ppv" es porque hay un margen donde puede no ser exacto.
//Los condicionales son para animar sea cual sea el sentido
if (ppv+parseFloat(id.style[prop])<valfinal){
if(prop=="opacity"||prop=="z-index"||prop=="zoom"){
id.style[prop]=parseFloat(id.style[prop])+ppv;
}else{
id.style[prop]=parseFloat(id.style[prop])+ppv+'px';
}
//Llamamos a la función. Usaremos "Timeout" para evitar colapsar funciones
Timeout=setTimeout("animar2('"+elem+"','"+prop+"',"+valinicial+","+valfinal+","+tpv+","+ppv+","+callback+")",tpv);
//Lo de antes pero en otro sentido
}else if(parseFloat(id.style[prop])-pp>>valfinal){
if(prop=="opacity"||prop=="z-index"||prop=="zoom"){
id.style[prop]=parseFloat(id.style[prop])-ppv;
}else{
id.style[prop]=parseFloat(id.style[prop])-ppv+'px';
}
Timeout=setTimeout("animar2('"+elem+"','"+prop+"',"+valinicial+","+valfinal+","+tpv+","+ppv+","+callback+")",tpv);
//Si se ha aproximado lo suficiente
}else{
//lo ponemos al valor final
if(prop=="opacity"||prop=="z-index"||prop=="zoom"){
id.style[prop]=valfinal;
}else{
id.style[prop]=valfinal+'px';
}

//callback con argumentos (si hay)
if (callback && typeof(callback) === "function") {
callback.apply(id,[valinicial,valfinal]);
}

//Ponemos "Timeout" como undefined (tendrá su utilidad)
Timeout=undefined;
//Terminamos
return false;
}

}

Ya está casi todo hecho pero así, aunque podamos animar, no podremos hacer algunos efectos chulos como ocultar y mostrar elementos con efectos.

Por eso he realizado les siguientes funciones valiéndome del callback: expandir, contraer, expandirContraer, fadeIn, fadeOut y fadeInOut.


/*====================================================
* Expandir
*/
function expandir(elem,tiempo){
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)
}
/*======================================================================
* Contraer
*/

function contraer(elem,tiempo){
var id=document.getElementById(elem);
if (window.getComputedStyle(id).getPropertyValue("display")=="none"){return false;}
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[0]+'px';
//Recordad que "arguments[0]" era la altura con la que empezó
});
}
/*======================================================================
* Expandir/Contraer
*/
function expandirContraer(elem,tiempo){
var id=document.getElementById(elem);
if(typeof(Timeout) != "undefined"){return false;}
if(window.getComputedStyle(id).getPropertyValue("display")=="none"){
expandir(elem,tiempo);
}else{
contraer(elem,tiempo);
}
}
/*======================================================================
* Fade in-out
*/
function fadeIn(elem,tiempo){
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)
}
function fadeOut(elem,tiempo){
var id=document.getElementById(elem);
if (window.getComputedStyle(id).getPropertyValue("display")=="none"){return false;};
animar(elem,"opacity",0,tiempo
,function(){
this.style.display="none";
this.style.opacity=arguments[0];
}
);
}
function fadeInOut(elem,tiempo){
var id=document.getElementById(elem);
if(typeof(Timeout) != "undefined"){return false;}

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

Ahora os dejo con la demo. Divertíos

Demo

Si os interesa el tema, os recomiendo un blog llamado #Web{border:none} , que aunque esté desactualizado, me ayudó por ejemplo, a darme cuenta de que la mejor forma de hacerlo era ponerlo todo en función de frames por segundo.