jueves, 18 de febrero de 2010

Menú acordeón con efecto deslizante, sólo con CSS y SMIL - Slidding accordion menu (CSS and SMIL only)

Ahora agregaremos al menú de tipo acordeón el efecto deslizante y algunos detalles de estilo. El efecto deslizante lo logramos en Internet Explorer mediante SMIL (o HTML+TIME, como lo llama Microsoft). Para el resto utilizamos "CSS3 transitions", actualmente funcional en Chrome y Safari, y en desarrollo para las próximas versiones de FireFox y Opera.

En la próxima entrada explicaremos algunos cambios menores en la estructura para facilitar el agregado de estilos, como así también algún aporte a la discución sobre estándares.

Ejemplo "vivo":



El código:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:time="urn:schemas-microsoft-com:time">
<head>
<title>Horizontal Accordion Menu</title>
<style type="text/css">
#menu {height:20px;}
#menu, #menu * {margin:0;padding:0;list-style:none;line-height:20px}
#menu .main {position:relative;float:left;overflow:hidden;background:yellow url(left.gif) no-repeat left top;padding-left:6px;}
#menu .sub {padding-right:6px;margin-top:-20px;padding-top:20px;}
#menu .sub li {margin-top:-20px;overflow:hidden;*overflow:visible}
#menu p {*position:relative;background:transparent url(right.gif) no-repeat right top;padding-right:6px;z-index:2}
#menu a {position:relative;display:block;width:50px;text-decoration:none;}
#menu p a {text-align:center;background-color:#000066;font-weight:bold;color:white;-webkit-transition:margin-right 1s linear;-mz-transition:margin-right 1s linear;-o-transition:margin-right 1s linear;}
#menu .sub a {color:red;background-color:yellow;}
#menu a.sub1 {left:50px}
#menu a.sub2 {left:100px}
#menu a.sub3 {left:150px}
#menu .sub a:hover, #menu a:active, #menu a:focus {margin-right:150px;z-index:2}
:root #menu a:hover {margin-right:150px;z-index:2}
time\:animate {-ms-behavior: url(#default#time2);behavior: url(#default#time2);}
</style>
</head>
<body>
<time:animate begin="a1.onmouseenter" end="a1.onmouseleave" dur="10s" targetelement="a1" attributename="marginright" keytimes="0;0.1;1" values="0px;150px;150px"></time:animate>
<time:animate begin="a2.onmouseenter" end="a2.onmouseleave" dur="10s" targetelement="a2" attributename="marginright" keytimes="0;0.1;1" values="0px;150px;150px"></time:animate>
<time:animate begin="a3.onmouseenter" end="a3.onmouseleave" dur="10s" targetelement="a3" attributename="marginright" keytimes="0;0.1;1" values="0px;150px;150px"></time:animate>
<ul id="menu">
<li class="main">
<p><a tabindex="1" id="a1" href="#">Main1</a></p>
<ul class="sub">
<li><a tabindex="2" class="sub1" href="#1">Sub11</a></li>
<li><a tabindex="3" class="sub2" href="#2">Sub12</a></li>
<li><a tabindex="4" class="sub3" href="#3">Sub13</a></li>
</ul>
</li>
<li class="main">
<p><a tabindex="5" id="a2" href="#">Main2</a></p>
<ul class="sub">
<li><a tabindex="6" class="sub1" href="#4">Sub21</a></li>
<li><a tabindex="7" class="sub2" href="#5">Sub22</a></li>
</ul>
</li>
<li class="main">
<p><a tabindex="9" id="a3" href="#">Main3</a></p>
<ul class="sub">
<li><a tabindex="10" class="sub1" href="#7">Sub31</a></li>
<li><a tabindex="11" class="sub2" href="#8">Sub32</a></li>
<li><a tabindex="12" class="sub3" href="#9">Sub33</a></li>
</ul>
</li>
</ul>
</body>
</html>

Las imágenes necesarias:
left.gif
right.gif

miércoles, 17 de febrero de 2010

Menú hoprizontal tipo acordeón con sólo CSS, explicado.

A continuación explicaré no tanto el funcionamiento, que es evidente, sino más bien algunas de las dificultades que enfrenté al diseñar este sistema de menú horizontal tipo acordeón, sólo con CSS, y completamente standar y funcional para IE6, IE7, IE8, FF (Nota: el bug en la versión 3.6 rompe la funcionalidad con tabulación), Chrome, Safari y Opera. Algunas otras dificultades y mejoras (así como algún que otro aporte a la discución sobre standars) las iré publicando posteriormente.

La lógica del funcionamiento de cualquier sistema de menú es la siguiente: mantener el submenú oculto hasta que el usuario interactúe con la opción correspondiente del menú principal. Para el caso en el que el menú tenga la semántica de listas anidadas, el mecanismo se traduce en mostrar el submenú cuando se interactúa con el item de la lista ("li:hover", "li:active", etc.) que contiene al enlace de la opción del menú principal.

Lamentablemente, este enfoque no funciona en IE6 pues sólo admite las pseudoclases en los enlaces. No hay ningún problema si no pensamos soportar esta funcionalidad en este navegador (existen propuestas sobre no agregar estilos a las páginas que le son servidas, a los efectos de instar a sus usuarios a actualizar de versión).

Si por el contrario, debido a un requerimiento, es necesario tener en cuenta al IE6, entonces el mecanismo del sistema de menú se desdobla: mostrar el submenú cuando se interactúa con el enlace del menú principal y mantener el submenú visible cuando se interactúa con los enlaces del submenú.

Hasta no hace mucho el sistema de menú más conocido que resolvía este problema incluso en el IE6 era el de Stuart Nicholls, que utilizaba comentarios condicionados (propios de IE) para agregar etiquetas que modificaban la semántica de la página: la sublista se anida dentro del enlace principal, y para evitar la respuesta imprevisible al aplicar estilos a esta semántica no válida, también se agrega etiquetas de tabla que le otorgan estabilidad (debido al algoritmo diferenciado que tiene el dibujo de las tablas en las páginas). Así, la sublista se transforma en descendiente del enlace principal.

Pero un nuevo sistema de menú ha sido desarrollado por Timo Huovinen. En éste no se modifica la semántica de las listas anidadas, sino que se aplican estilos cuando se interactúa tanto con el enlace del menú principal como con los enlaces de la sublista que permiten mantener visible la sublista completa. En su caso, como el menú se despliega verticalmente, el mecanismo consiste en hacer flotantes (propiedad "float") tanto al enlace principal como la sublista que le continúa, ambos dentro de un bloque contenedor con ancho fijo (los items de la lista principal). Como los bloques flotantes se estrechan hasta el ancho de lo que contienen, cuando se interactúa con los enlaces y estos modifican su estilo para que entre ambos bloques flotantes excedan el ancho fijo del bloque contenedor, se produce el mismo efecto (tanto sean los enlaces de la sublista como el del menú principal): la sublista "salta" o "cae" a una nueva línea y el largo del bloque contenedor crece (haciendo funcionales a los enlaces). Por supuesto, también se utilizan márgenes positivos y negativos para conseguir exactamente la visualización deseada.

Para menú tipo acordeón el enfoque tuvo que cambiar puesto que lo que debe aumentar es el ancho del bloque contenedor del enlace principal y la sublista.

El primer intento consistió en transformar todos los bloques en elementos en línea ("display:inline" y "display:inline-block") y aplicar o quitar márgenes negativos al enlace principal (lo que reducía la funcionalidad a sólo poder "clickear" en el enlace para que el efecto fuera persistente). Esto, si bien hacia el menú de un ancho totalmente fluído, no resultó en ninguna versión de IE (enlaces bajo márgenes de otro objeto no son funcionales).

El segundo intento me acercó a la solución: fijar el ancho de los enlaces, comprimir todo a una sola línea mediante margenes negativos, espaciar los enlaces mediante un márgen proporcional a su orden (esto agregó atributos de clase a la etiqueta de enlace, ya que IE6 carece del selector "next sibling"). Para que el recorte del desborde ("overflow: hidden") dejara visible alternativa mente sólo la opción principal o agregara el submenú, el estilo básico de los enlaces de la sublista tiene su posición definida como absoluta ("position:absolute") mientras que bajo las pseudo clases su posición es estática, consiguiendo que se compute o no su ancho para definir el ancho del bloque contenedor del enlace principal y la sublista. Pero aquí me topé con un error en Webkit (Chrome y Safari): no es posible modificar la propiedad de posición cuando el final del selector tiene pseudoclases. Esto nos devolvía, en el caso de Webkit, al enfoque original de aplicar la pseudoclase al bloque contenedor, lo que nos quitaba la funcionalidad de "clickear" para desplegar el submenú o de recorrerlo con la tecla de tabulación.

¿Cómo evitar que el ancho de los enlaces se computaran para el ancho del bloque contenedor sin modificar la propiedad de posición? La solución: espaciar los enlaces mediante desplazamientos relativos ("position:relative" y "left" o "right"), lo que logra que sólo se computen los anchos (que son fijos e iguales) y no el desplazamiento, además de mantener funcional al recorte del desborde (mostrando el submenú sólo cuando se interactúa con el enlace principal porque mediante un márgen horizontal éste modifica su ancho y en consecuencia el del bloque contenedor). Cuando se pasa a interactuar con los enlaces de la sublista, estos deben aumentar su ancho con márgenes horizontales de manera de incluir el submenú y la opción del menú principal. Los diferentes desplazamientos de cada opción del submenú agrandan la hoja de estilo, si bien no mucho, pero pueden realizarse con seguridad de antemano. Esto restringe la funcionalidad del menú dejando fijo el ancho de las opciones del menú como también la cantidad máxima de opciones del submenú.

Menú tipo acordeón sólo con CSS - CSS only accordion menu

Después de un par de días de investigación inspirada en el ejemplo de Timo Huovinen, he logrado una versión de menú horizontal tipo acordeón con sólo CSS (sin efecto deslizante, desde ya). Aquí la comparto para quién pueda serle útil.


No tiene prácticamente ningún estilo. Sólo a manera de ejemplo se agregaron algunos colores de fondo para que quede ejemplificado en el código dónde deben aplicarse correctamente (estoy pensando en el fondo de los submenús con menos opciones que la predeterminada).

La semántica de la página no difiere de la de cualquier otro menú con listas anidadas, y he tratado de mantenerla en la mínima e indispensable pensando en su posible automatización (proximamente agregaré ejemplos).

La principal característica es que el ancho de las opciones del menú es fijo, como así también la cantidad de opciones máximas del submenú. En una próxima entrada explicaré el mecanismo por el cual funciona junto con los problemas que tuve que enfrentar para conseguir una versión que funcione en IE6, IE7, IE8, FF, Chrome, Safari y Opera.


Les dejo el código.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Horizontal Accordion Menu</title>
<style type="text/css">
#menu {height:20px}
#menu, #menu * {margin:0;padding:0;list-style:none;line-height:20px;}
#menu .main {float:left;overflow:hidden;position:relative;background-color:yellow;}
* html #menu .main {display:inline;}
*:first-child+html #menu .main {display:inline;}
#menu .sub li {margin-top:-20px}
#menu a {position:relative;display:block;width:50px;background-color:#000066;font-weight:bold;color:white;text-align:center;}
#menu .sub a {background-color:yellow;z-index:10}
#menu a.sub1 {left:50px;}
#menu a.sub2 {left:100px;}
#menu a.sub3 {left:150px;}
#menu a:hover, #menu a:active, #menu a:focus {margin-right:150px;}
</style>
</head>
<body>
<ul id="menu">
<li class="main"><a tabindex="1" href="#">Main1</a>
<ul class="sub">
<li><a tabindex="2" class="sub1" href="#1">Sub11</a></li>
<li><a tabindex="3" class="sub2" href="#2">Sub12</a></li>
<li><a tabindex="4" class="sub3" href="#3">Sub13</a></li>
</ul>
</li>
<li class="main"><a tabindex="5" href="#">Main2</a>
<ul class="sub">
<li><a tabindex="6" class="sub1" href="#4">Sub21</a></li>
<li><a tabindex="7" class="sub2" href="#5">Sub22</a></li>
</ul>
</li>
<li class="main"><a tabindex="9" href="#">Main3</a>
<ul class="sub">
<li><a tabindex="10" class="sub1" href="#7">Sub31</a></li>
<li><a tabindex="11" class="sub2" href="#8">Sub32</a></li>
<li><a tabindex="12" class="sub3" href="#9">Sub33</a></li>
</ul>
</li>
</ul>
</body>
</html>