en CSS, responsive

Menú responsive – Tutorial CSS

En este tutorial vamos a ver un pequeño componente CSS muy útil: un menú responsive de dos niveles compatible con mobile y tablet, sensible a la resolución de las pantallas pero también a su orientación (vertical u horizontal).

Antes de empezar a construir el menú responsive con CSS, primero tendremos que tener claro los estados de dicho menú para cada pantalla. Por eso hemos de partir de un diseño que los defina y para ello he preparado un pequeño wireframe con varios dispositivos donde se resaltan en verde las zonas más cómodas de acceso basadas en las disposiciones ergonómicas más comunes para cada dispositivo.

Zonas de accesibilidad optimas para menús responsive

Partiendo de ello podremos ya diseñar nuestro menú responsive, pero para hacerlo más interesante le añadiremos un segundo nivel. Con esas premisas ya tenemos los elementos necesarios para trabajar. Éste sería el resultado a conseguir:

Responsive menu example

Si queréis podéis ver el resultado en vivo del menú responsive. Ahora veamos los pasos para construirlo.

Paso 1, construir el menú html

Con la llegada de HTML5 surgieron discrepancias sobre cual es la estructura semántica  correcta para un menú. En mi humilde opinión, mientras HTML5 no incluya un elemento <navitem>, el tag <nav> por sí solo, no nos provee de la estructura necesaria para un menú, por lo que añadiremos una lista de enlaces que, a día de hoy, es el marcado que nos provee de mayor carga semántica y nos facilita una jerarquía idónea para el tipo de menú que vamos a hacer. De momento no pondremos los enlaces para que quede más clara la estructura de nuestro menú responsive.

<nav>
<ul class="menu">
 <li>Opción 1
   <ul>
     <li>Sub Opción 1.1</li>
     <li>Sub Opción 1.2</li>
     <li>Sub Opción 1.3</li>
     <li>Sub Opción 1.4</li>
   </ul>
 </li>
 <li>Opción 2</li>
 <li>Opción 3</li>
   <ul>
     <li>Sub Opción 3.1</li>
     <li>Sub Opción 3.2</li>
     <li>Sub Opción 3.3</li>
     <li>Sub Opción 3.4</li>
   </ul>
 <li>Opción 4</li>
</ul>
</nav>

Paso 2, determinar los breakpoints

Este paso será genérico para todos los componentes de la aplicación/web, pero va bien que nos acostumbremos a seguir una metodología de trabajo. Por lo tanto el segundo paso es determinar qué adaptaciones queremos hacer y para qué tipo de dispositivos. De forma que nuestro menú responsive necesitará breakpoints entre los siguientes estados:

  1. Desktop horizontal
  2. Tablet horizontal
  3. Tablet vertical (igual que mobile)
  4. Móvil vertical y horizontal

Representamos estos breakpoints con las siguientes Media Queries:

/*Aquí las CSS para Desktop (también para híbridos o tablets extremadamente grandes)*/
@media only screen and (max-width: 1280px) and (min-aspect-ratio: 16/10), 
only screen and (max-width: 1024px) and (aspect-ratio: 4/3), /*iPad y similares*/
only screen and (orientation: portrait), /*Para cuando en desktop convertimos la ventana a vertical*/
only screen and (max-width: 768px) and (orientation: landscape) {
/*Aquí las CSS para Tablet o cualquier dispositivo más pequeño*/
}
@media only screen and (max-width: 768px){
/*Aquí las CSS para tablet y móvil verticales y móvil horizontal*/
}

En este caso he usado un esquema de media queries desktop first. Podéis usar mobile first si así lo consideráis. Lo que sí os recomiendo es leer Media queries breakpoints – claves del responsive design, donde explico con más detalle los conflictos que os encontraréis al definir los límites entre unos dispositivos y otros, y la forma de resolverlos.

Paso 3, aplicar los estilos al menú

Vamos pues en primer lugar con el menú horizontal para desktop. Si habéis leído otros artículos míos conoceréis mi aversión al uso indiscriminado de float 😛. Por ello vamos a usar otra técnica igualmente sencilla y que nos permitirá pasar de horizontal a vertical con gran facilidad y, de propina, alinear el menú a derecha, iquierda y centro sin usar float.

Menú para desktop

Convertir el menú en horizontal

Antes de nada lo primero que haremos será un pequeño reset:

* {
 box-sizing:border-box; /*Para no tener que andar restando bordes y paddings de las medidas*/
 margin: 0; padding: 0; /*Eliminamos espacios por defecto del html, body o ul que usaremos*/
 }

y seguidamente pondremos el primer nivel del menú en horizontal:

.menu > li {
  display: inline-block; /*Alineamos los li del primer nivel*/
}

y posicionamos el segundo nivel del menú:

.menu ul {
 position:absolute;
 left: 0;
 top:100%;/*Lo posicionamos en el borde inferior de su padre*/
 }

Clases para ocultar y mostrar el menú

Ahora, preparamos la clase «closed» que posteriormente añadiremos y quitaremos a los <li> del primer nivel con jQuery para ocultar y mostrar el sub-menú.

/*Creamos un efecto cortina*/
.menu ul li {
 display: block;
 max-height: 3.125rem; /*Es el tamaño que tendrá cada item del listado abierto*/
 opacity: 1;
 -webkit-transition: max-height 0.5s, opacity 0.5s;
 transition: max-height 0.5s, opacity 0.5s;
 white-space: nowrap;
 }
.menu li.closed li {
 overflow: hidden;
 max-height: 0; /*Es el tamaño que tendrá cada item del listado cerrado*/
 opacity: 0;
 -webkit-transition: max-height 0.5s, opacity 0.5s;
 transition: max-height 0.5s, opacity 0.5s;
 }

La añadimos manualmente en nuestro html para comprobar que funciona y lo dejamos así hasta que terminemos con el primer nivel del menú.

Limpiamos espacios indeseados

Si seleccionamos los elementos alineados de ese primer nivel, observaremos que entre ellos quedan unos espacios. Eso es debido a los saltos de línea que usamos en nuestro código, ya que el navegador los interpreta como espacios al alinear los elementos del listado. Si dispusiésemos los <li> en una sola línea de código, sin espacios, solucionaríamos el problema. Pero vamos a ver la solución CSS.

.menu {
  font-size: 0; /*Eliminamos los espacios entre los li*/
}
.menu > li {
  display: inline-block;
  position: relative; /*Le damos un contexto al menú desplegable que tendrá una posición absoluta*/
  font-size: initial; /*Devolvemos el tamaño de fuente inicial al li o le asignamos uno específico.*/
}

O en su lugar podemos hacer esto:

.menu {
  font-size: 0; /*Eliminamos los espacios entre los li*/
}
.menu * { /*Aplicamos los estilos de fuente a todos los elementos del menú*/
 font: normal 1rem/1 "Myriad", Arial, Helvetica, sans-serif;
 color: #334;
 }
.menu > li {
 display: inline-block;
 position: relative; /*Le damos un contexto al menú desplegable que tendrá una posición absoluta*/
}

¡¡TERMINADO!! Ya podemos incluir los enlaces y algunos estilos, y verlo todo en conjunto antes de continuar con la parte responsive de nuestro menú.

Añadimos algunos estilos al menú

body {
 background: #334;
 }
header{
 background: #fff;
 }
.menu {
  margin: 0;
  font-size: 0;
  color: #999;
}
.menu * {
 font: normal 1rem/1 "Myriad", Arial, Helvetica, sans-serif;
 color: #334;
 }
.menu > li {
  display: inline-block;
  position: relative;
}
.menu > li + li { /*Todos los li precedidos por otro, es decir, todos menos el primero*/
  border-left: solid 1px #334;
}
.menu li a {
 display: inline-block;
 padding: 0.625rem 1.25rem;
 text-decoration: none;
 }
.menu li:hover {
  background: #fc9;
  cursor: pointer; /*Puedes eliminarlo una vez incluyas enlaces*/
 }
.menu li:hover ul {
  display: block;
  position:absolute;
  left: 0; /*right: 0; si alineamos el menú a la derecha*/
  top:100%;
  border-top: solid 4px #F96;
 }
.menu ul {
 display: block;
 position:absolute;
 left: 0;
 top:100%;
 border-top: solid 0.25rem #F96;
 background: #fed;
 }
 .menu ul:before {
 content: "▲";
 position: absolute;
 top: -0.875rem;
 left: 2.5rem; /*right: 2.5rem; si alineamos el menú a la derecha*/
 color: #F96;
 }
 .menu ul li {
 display: block;
 max-height: 3.125rem;
 opacity: 1;
 -webkit-transition: max-height 0.5s, opacity 0.5s; /* Safari */
 transition: max-height 0.5s, opacity 0.5s;
 white-space: nowrap;
 }
 .menu .closed li {
 overflow: hidden;
 max-height: 0;
 opacity: 0;
 -webkit-transition: max-height 0.5s, opacity 0.5s; /* Safari */
 transition: max-height 0.5s, opacity 0.5s;
 }

Alineamos el menú a la izquierda, derecha o centro.

¡Facilísimo! Al haber convertido los <li> en elementos inline podemos alinearlos como si fuesen texto. Así, la izquierda es la alineación por defecto, no tendréis que hacer nada. Para el caso de derecha o centro, sólo tendréis que aplicar la propiedad text-align: right/center; al menú.

.menu {
  margin: 0;
  font-size: 0;
  color: #999;
  text-align: right; /*Nuestros li se desplazan a la derecha*/
}

CSS para nuestro menú responsive

Antes de entrar en materia con los breakpoints, deberemos añadir una línea de código a nuestro <head>:

<meta name="viewport" content="initial-scale=1">

De esta forma forzamos que nuestra escala inicial sea siempre 1, arrebatando el control al navegador que, por norma general, intentará mantener una relación entre los elementos proporcional a la diferencia de tamaño del viewport respecto de un desktop.

Sin esta línea las Media Queries no tendrían efecto.

Menú para tablets en landscape

Esta es la disposición más común para muchas tareas en tablet, ya que nos da más agilidad al escribir en el teclado y manejarnos por los contenidos con dos dedos en lugar de uno a la vez que sostenemos la tablet con dos manos. Por lo tanto lo más recomendable es adaptar nuestro menú a la posición de las manos del usuario para evitar lo opuesto. Para ello lo primero que haremos será devolver la verticalidad al menú.

@media only screen and (max-width: 1280px),.../*ver Paso 2*/ {
  html, body {
    height: 100%; /*Igualamos la altura del body a la de la ventana*/
   }
   .menu {
     width: 10rem;
     height: 100%; /*Ahora igualamos la altura del menú a la del body*/
     text-align: center;
   }
.menu * { /*Ajustamos el tamaño del texto y de todos los elementos para una mejor experiencia táctil*/
 font-size: 1.5rem;
 }
}

Menú para tablets en portrait

Puesto que la manera más común en que los usuarios sostienen las tablets en portrait es con una mano, pulsando o deslizando con la otra, mantendremos el menú en la parte superior, lejos de la mano que sostiene el dispositivo para evitar pulsaciones indeseadas.

En este punto probablemente comience a escasear el espacio, en especial si el menú compartiese la cabecera con otros elementos como el logo u otro menú, quedando todo muy comprimido. Por ello, vamos a esconderlo tras una hamburguesa compartiendo ya el CSS con los móviles como veremos a continuación.

Menú para móviles en portrait y landscape

Añadiremos el botón de menú que comentábamos, la hamburguesa. Será el primer hijo del elemento <nav>, justo antes del menú.

<button type="button" class="hamburguer"></button>

Y antes de saltar dentro de la media query, escondemos la hamburguesa para el resto de estados en los CSS generales:

.hamburguer {
 display: none;
 }

Ahora, manteniendo el menú vertical y visible (más tarde lo esconderemos con jQuery), horizontalizamos de nuevo la cabecera y mostramos la hamburguesa con sus estilos.

@media only screen and (max-width: 768px){
  header { /*Devolvemos el estado horizontal inicial a la cabecera. El menú se adaptará a ella*/
   width: initial;
   height:initial;
   }
.hamburguer {
   display: inline-block;
   width: 3.125rem;
   height: 3.125rem;
   background: #fff url(hamburguer.png) no-repeat center center;
   border: 0;
   }
.hidden {
   display: none; /*esta clase nos permitirá ocultar el menú mediante jQuery*/
  }
}

Ya podemos ver cómo ha quedado el menú responsive en desktop, tablet y móvil. \o/

Paso 4, dinamizar el menú con jQuery.

Este es el código jQuery que necesitarás para que tu menú CSS oculte y muestre el segundo nivel.

$( document ).ready(function() {
  var menuBurguer = function() {
    //Ocultamos o mostramos menús en función del ancho de la página
    var winWith = $(window).outerWidth();
    if(winWith <= 768) {
      $('.menu').addClass('hidden'); //Ocultamos cualquier menú que haya en nuestra página
    } else {
      $('.menu').removeClass('hidden');
    }
  }
  menuBurguer(); //Inicializamos menuBurguer

  var clickBurguer = function() {
  //Ocultamos o mostramos el menú asociado a la hamburguesa que ha sido pulsada
   $('.hamburguer').on('click', function(){
     var thisMenu = $(this).siblings('.menu');
     $(thisMenu).toggleClass('hidden'); //Cambiamos entre los estados visible y oculto
   });
  }
  clickBurguer(); //Inicializamos clickBurguer

  var closeMenu = function(){ //Ocultamos y mostramos submenús
    $('.menu > li').has('ul').addClass('closed'); //Cerramos todos los submenús
  }
  closeMenu();
  $('.menu > li a').on('click', function(){
    var dady = $(this).parent();
    if($(dady).is('.closed')){
      closeMenu();
      $(dady).removeClass('closed');
    } else {
    closeMenu();
    }
  });
  $(window).resize(function(){
    menuBurguer(); //Llamamos a menuBurguer al cambiar el tamaño de la ventana
  });
});

Podéis probar el resultado aquí: menú responsive

Espero que os sea útil. Si tenéis cualquier duda podéis dejarla en los comentarios y os la resolveré en el mismo día.

Escribe un comentario

Comentario

    • Hola Xavier,
      pues no lo sé, no veo tu código 😛
      Pon aquí un enlace si quieres a tu página y le echo un vistazo.
      Igual «No me funciona» es un poco genérico ¿Puedes especificar? ¿Qué es lo que no te funciona concretamente?

      Espero tu respuesta para poder ayudarte
      Saludos