Ir directamente al contenido de esta página

Un acordeón fallido con CSS3

Como ya he mostrado en entradas anteriores —para menús desplegables o replicando la barra de Apple—, creo que es una buena idea emplear los efectos de transición de CSS3 para crear animaciones de interfaz en lugar de JavaScript puro. Siguiendo en esta línea, he estado probando crear un acordeón mediante estilos sin más. El resultado ha sido un fracaso, pero uno muy enriquecedor.

El punto de partida

La idea inicial era emplear la pseudoclase :target para variar la altura de los elementos con los contenidos a mostrar. El marcado es el siguiente:


<ul id="menu1">
  <li>
    <a href="#menu11">Lorem ipsum</a>
    <div id="menu11">
      <p lang="la">Lorem ipsum dolor sit amet […] </p> 
    </div>
  </li>
  <li>
    <a href="#menu12">Lorem ipsum</a>
    <div id="menu12">
      <p lang="la">Duis aute irure dolor […] </p>
    </div>
  </li>
  …
    <!-- Todos los elementos que hagan falta -->
  …
</ul>
 

Con asignar una altura inicial de 0 a los div y otra altura a su :target, parecía suficiente:


ul div{
  background: #FFF;
  -moz-transition: .75s;     /* Que no se diga que somos en parte responsables */ 
  -ms-transition: .75s;      /* de la debacle de -webkit-. */
  -webkit-transition: .75s;
  -o-transition: .75s;       /* Si no se entiende la referencia, */
  transition: .75s;          /* aquí hay un compendio de vínculos relevantes. */
  overflow: hidden;
  height: 0;
}
  …
#menu1 div:target{
  height: 16em;
}
 

Así, el ejemplo funciona… pero desgraciadamente funciona sólo como ejemplo. ¿A qué me refiero? A que aislado, el acordeón se comporta como deseamos, pero incluido en una página real mostraría un comportamiento tarado. Si incluimos otro acordeón, cuando activamos uno de los dos el elemento desplegado del otro se contrae al perder el foco del vínculo. Igual ocurriría al hacer clic sobre cualquier otro vínculo del documento.

JavaScript al rescate

No obstante, empleando algo de JavaScript, podemos corregir este problema y algunos otros detalles, como son:

El peso de la animación, sin embargo, sigue recayendo sobre las propiedades de CSS3. Aquí está la prueba, y aquí el código del script:


var AC = {
  $: function(el){return document.getElementById(el);},
  $$: function(et,el){el===undefined ? el = document : el = el;return el.getElementsByTagName(et);},
  $$$: function(cl,el){el===undefined ? el = document : el = el;return el.getElementsByClassName(cl);},
  cl: 'ac',
  t: '.75s',
  cA: 'AC'+(new Date().getTime()),
  alts: [],
  cntr: function(x){
    var ex = AC.$(x[0].id);
    var cT = (ex.className.toString()).replace(' expandir','');
    ex.className = cT;
    ex.style.height = '0';
  },
  estado: function(x){
    x.preventDefault() ? x.preventDefault() : x.returnValue=false;
    var t = x.target.toString().split('#'), it = t[t.length-1], p = /expandir/i;
    var ex = AC.$$$('expandir',AC.$(it).parentNode.parentNode);
    if(!(AC.$(it).className.match(p))){ 
      if(ex[0]!==undefined){
        AC.cntr(ex); 
      };
      AC.$(it).className+=' expandir';
      AC.$(it).style.height = AC.alts[it]+'px'; 
      AC.$(it).focus();
    }else{
      AC.cntr(ex); 
    };
  },
  ini: function(){
    var eA = document.styleSheets[document.styleSheets.length-1];
     eA.insertRule('.'+AC.cA+'{overflow:hidden;-moz-transition:'+AC.t+';-ms-transition:'+AC.t+';-webkit-transition:'+AC.t+';-o-transition:'+AC.t+';transition:'+AC.t+';}',eA.cssRules.length); 
    var m = AC.$$$(AC.cl);
    for(i=0;i<m.length;i++){
      var b = AC.$$('li',m[i]); 
      for(j=0;j<b.length;j++){
        AC.$$('a',b[j])[0].onclick = AC.estado;
      }
      var d = AC.$$('div',m[i]);
      for(k=0;k<d.length;k++){
        var al = d[k].offsetHeight;
        AC.alts[d[k].id] = al;
        d[k].className+=AC.cA;
        d[k].style.height='0';
        d[k].tabIndex = '-1';
      }
    } 
  } 
}
window.onload = AC.ini;
 

Hay dos variables de configuración para quien quiera emplearlo:

El script lo que hace es buscar los menús con la clase indicada y almacenar en una matriz las alturas de los elementos para modificarlas posteriormente. Para que no sea necesario añadir una regla en la hoja de estilo con la definición de la transición, la crea al vuelo y por medio de insertRule la añade a la última hoja de estilo de la página —tranquilos, le asigno un número aleatorio para que no entre en conflicto con ninguna de las reglas ya definidas (bueno, a menos que alguien haya definido una clase como AC1329215346055…)—. El resto del código simplemente se encarga de coordinar los estados de los elementos de los menús.

Accesibilidad

Como es un tema que me preocupa, me agrada comprobar que la funcionalidad está disponible para navegación por medio de teclado. Además, hay tres posibilidades cubiertas:

  1. El usuario no cuenta con JavaScript: los contenidos aparecen desplegados sin más.
  2. El usuario cuenta con JavaScript, pero el navegador no soporta transiciones de CSS: los deplegables se abren y cierran de manera inmediata sin animación.
  3. El usuario cuenta con JavaScript, y el navegador soporta transiciones de CSS: perfecto.

No está mal para un experimento fallido, ¿verdad?

Esta entrada se publicó el 1 de marzo de 2012, se archivó en , y fue etiquetada como , , . Autor: Saúl González Fernández. Hay 5 comentarios ›.

Comentarios

  1. Mateo dice:

    Hola, de todas formas, el ejemplo fallido unicamente con CSS3, no es tan fallido, ¿o sí? Es decir, quizás para una seccion donde necesitemos tener abierto siempre el menú, no sirva… pero para un menú donde sólo se desplieguen links a otras paginas del sitio, esta bien, ¿no? Es decir, no necesitamos que el acordeon quede infinitamente abierto, es más, que se cierre al apretar alguna otra parte de la pagina hasta me parece que queda bien… ¿Tiene algun otro fallo el menú?

  2. Mateo dice:

    Tengo una duda general del blog: en la seccion de CSS, dice que hay 16 resultados, XHTML 13, JS 14 y así… pero la página sólo muestra 10 resultados y no hay boton de ‘siguiente pagina’, o al menos no lo veo…

  3. Mateo dice:

    Hola, tercer comennt que posteo acá…

    ¡¡¡Muchas gracias por el tutorial!!! Me sirvió bastante, solo tengo una duda, que he intentado resolver, pero no lo logro…

    Para practicar el tutorial, hice un menú sencillo, y le aplique la animacion. Ahora quiero que tenga otro submenú, pero no logro que me aparezca. Me aparece el primero, pero el otro submenu no me aparece. En el submenu “NATURALEZA>>” (adentro de ‘PORTFOLIO’), debería aparecer otro submenú, pero no logro que aparezca, ¿alguien me puede ayudar? Dejo el link al menú…

    http://dl.dropbox.com/u/28226523/menudesplegablecss.html

  4. Saúl González Fernández dice:

    @Mateo: La limitaciones del menú son que no funciona en Explorer por debajo de la versión 9, y lo que comentábamos de que por defecto se «cierra» en el momento en que se hace clic en otra zona de la página. Si como comentas se emplease para incluir vínculos externos a una página, posiblemente no sea un comportamiento demasiado erróneo.

    Sobre el submenú de tu prueba de ejemplo, creemos que el problema está en la declaración de .nav li > ul, donde has asignado overflow: hidden;: el submenú de «NATURALEZA» queda oculto.

    Y en cuanto al blog, sí, gracias, no nos habíamos dado cuenta de que hay un problema con la paginación.

  5. Saúl González Fernández dice:

    @Mateo: Por cierto, un consejo: cuando emplees una propiedad con un prefijo propietario —como -webkit-transition— incluye los prefijos de los demás navegadores. Con Prefixr sólo tienes que pegar tu hoja de estilo y te los incluye de forma automática.

¿Algún comentario?

* Los campos con un asterisco son necesarios

Últimos proyectos

© Digital Icon, S.L., 2007 – 2017