Icono hamburguesa

Mario González - Blog sobre desarrollo web

Icono animado para el menú de móvil

Cuando accedéis a una web desde un móvil, lo más seguro es que el menú de navegación esté oculto para dejar más espacio disponible al contenido principal, y que haya un icono de tres líneas horizontales para desplegar el menú. A ese icono se le llama hamburguesa. En este tutorial vamos a ver cómo hacer que el icono de la hamburguesa se convierta en una cruz cuando se despliegue el menú, y que vuelva a su apariencia hamburguesil tras cerrarlo.

Icono hamburguesa

Maquetación del icono

Lo primero será crear el icono. Si no fuéramos a animarlo, quizás nos serviría cualquier Icon Font y la implementación sería más rápida, pero como queremos animar cada línea de manera independiente, tenemos que crear tres líneas horizontales paralelas. No vamos a entrar en la maquetación general, es decir, en cómo ubicar el icono dónde queremos, simplemente vamos a ver cómo crearlo. Quedará en vuestras habilidosas manos la tarea de hacer que se vea como y donde queréis. Si ya sabéis cómo maquetarlo, podéis saltar directamente a cómo animarlo.


<div class="menu-activador">
    <a href="#">
        <span class="menu-activador-linea"></span>
        <span class="menu-activador-linea"></span>
        <span class="menu-activador-linea"></span>
    </a>
</div>

Usaremos un elemento contenedor, en este caso un div, que albergará el link y las tres líneas. En realidad podríamos saltarnos el div y darle la clase menu-activador directamente al a, pero por claridad vamos a dejarlo así.

Hemos hecho que las tres líneas estén englobadas en un a por cuestiones de usabilidad. Desde JavaScript capturaremos el click, con lo que podríamos pensar que el a no hace falta, pero entonces estaríamos impidiendo que se pueda abrir el menú desde el teclado (hay muchos usuarios que navegan con el teclado).

Tenemos entonces un contenedor para todo el icono, dentro un a que sólo cumple la función de link, y dentro de éste los tres elementos span, que serán nuestras líneas horizontales. Vamos con el CSS.

El tamaño del icono lo vamos a determinar desde el propio contenedor menu-activador, es decir, le daremos a este elemento un ancho y un alto, y haremos que sus elementos hijos lo rellenen.


.menu-activador {
    width: 30px;
    height: 20px;
    border: 1px solid #f00;
}

Como ninguno de los elementos hijos del div tiene contenido, aunque le demos ancho y alto no veremos nada, por eso le ponemos temporalmente un borde, así nos podemos hacer una idea de las proporciones que tendrá nuestro icono. Lo siguiente es hacer que el a ocupe todo el espacio disponible del div, para que todo el icono sea clicable. Como sabéis, el a es un elemento de tipo inline, con lo que no nos bastará con darle ancho y alto. Podríamos convertirlo a inline-block, pero en este caso lo vamos a convertir a flex, luego veremos por qué.


.menu-activador {
    width: 30px;
    height: 20px;
    border: 1px solid #f00;
}
.menu-activador a {
    display: flex;
    height: 100%;
    border: 1px dotted #00f;
}

He vuelto a usar otro borde temporal para poder ver cuánto ocupa el elemento. Al convertir el a en un contenedor flex, inmediatamente pasará a tener todo el ancho disponible. Para hacer lo mismo con la altura, tenemos que darle nosotros el height: 100%, y así cogerá toda la altura disponible.

Con esto nos aseguramos de que tenemos ya el espacio para nuestro icono preparado, así como su función de link:

Contenedor icono hamburguesa

Nos quedan las tres líneas. Esta tarea es bien fácil, sólo tenemos que darles una altura (2px, por ejemplo) y un color de fondo. Pero recordemos que cada línea es un span, que es un elemento de tipo inline, y por tanto no nos va a coger todo el ancho disponible. Aquí es donde entra flexbox en acción.

Al haber hecho que el contenedor de los span sea un contenedor flex, desde el propio elemento contenedor podemos determinar cómo se van a ver sus elementos hijos, sin tener que tocar el estilo de éstos.


.menu-activador a {
  display: flex;
  flex-direction: column;          /* Hijos distribuidos en vertical */
  justify-content: space-between;  /* Hijos distribuidos ocupando todo el espacio */
  height: 100%;  
  border: 1px dotted #00f;
}
.menu-activador-linea {
  height: 2px;
  background-color: #000;
}

Como los span se tienen que distribuir en vertical, hemos dicho que la dirección principal del contenedor flex sea la vertical. Sólo con esto los span ya cogen todo el ancho disponible. Además, hemos dicho que los elementos hijos se distribuyan a lo largo de todo el espacio disponible. Con esto ya tenemos las tres líneas dibujadas sin tener que haber tocado sus estilos más que para el color y el grosor. Si os perdéis con el modelo flexbox, en CSS-Tricks tenéis una guía bastante detallada de su funcionamiento.

Ahora podemos quitar los bordes temporales de los contenedores y ver cómo queda nuestro icono:

Icono hamburguesa

Funcionalidad del icono

Ya tenemos nuestro icono maquetado. Ahora, antes de entrar en cómo animarlo, vamos a hacer que funcione. Ésta va a ser una sección muy corta en la que, mediante JavaScript, capturaremos el click sobre el icono y le añadiremos o quitaremos una clase al mismo.


(function() {

    const hamburguesa = document.querySelector('.menu-activador a');

    hamburguesa.addEventListener('click', function(event) {
        event.preventDefault();
        this.classList.toggle('menu-abierto');
    })

}());

Con estas líneas estamos simplemente indicando que cuando se haga click sobre el icono, a éste se le añada la clase menu-abierto, o se le quite si ya la tenía. Fijaos en que capturamos el evento y lanzamos un preventDefault(), para que el navegador no siga el link (href="#").

Queda fuera de este artículo toda la parte de cómo abrir o cerrar un menú, que se puede hacer de muchas maneras distintas. En cualquier caso, sería este click que estamos capturando el que tendría que lanzar las acciones necesarias.

Para comprobar que funciona, vamos a cambiar (temporalmente) el color de las líneas para cuando el elemento a tiene la clase menu-abierto:


a.menu-abierto .menu-activador-linea {
  background-color: #f00;
}

Animando el icono

Aunque podemos hacer muchas animaciones, en este caso nos vamos a centrar en la más simple: para que las tres líneas se conviertan en una cruz, la de enmedio va a desaparecer, las otras dos van a girar y se van a acercar entre sí. Vamos por pasos, primero maquetaremos la cruz y luego añadiremos la animación de transición entre la hamburguesa y la cruz.


a.menu-abierto .menu-activador-linea:nth-child(2) {
    opacity: 0;
}

Ahora, al hacer click sobre el icono, veremos que la segunda línea desaparece. Usaremos opacity: 0 y no display: none porque después queremos añadir una transición.

Lo siguiente será que las otras dos líneas giren. La de arriba tiene que girar 45 grados en el sentido de las agujas del reloj, y la de abajo 45 grados en el sentido contrario.


a.menu-abierto .menu-activador-linea:nth-child(1) {
    transform: rotate(45deg);
}
a.menu-abierto .menu-activador-linea:nth-child(3) {
    transform: rotate(-45deg);
}

Con este cambio, al hacer clic vemos que ambas líneas giran, pero no se cruzan en el centro. Para ello tenemos que acercarlas entre sí. La distancia que tiene que desplazarse cada una la he calculado a ojo, quizás haya algún método matemático para hacerlo, pero yo no lo conozco. Si jugáis con el inspector del navegador y vais cambiando las unidades mientras veis cómo quedan las líneas. Con las medidas que estamos usando, las líneas se tienen que acercar 9px cada una.


a.menu-abierto .menu-activador-linea:nth-child(1) {
    transform: translateY(9px) rotate(45deg);
}
a.menu-abierto .menu-activador-linea:nth-child(3) {
    transform: translateY(-9px) rotate(-45deg);
}

Fijaos que el desplazamiento se hace con la misma propiedad CSS que la rotación, transform. No podemos ponerlo en dos líneas distintas, porque entonces la segunda machacaría a la primera, y lo único que conseguiríamos es rotación sin desplazamiento:


a.menu-abierto .menu-activador-linea:nth-child(1) {
    transform: translateY(9px);  /* Ésta propiedad se anula con la siguiente */
    transform: rotate(45deg);
}

También es importante que os fijéis en que primero hemos desplazado y luego hemos rotado. Si rotamos primero y desplazamos después, el resultado es diferente y no quedarán unidas por su punto central (habría que recalcular y sustituir los 9px por los que toquen).

Si ahora hacéis click en el icono, veréis cómo se transforma en una cruz, y al hacer click otra vez vuelve a su estado inicial. Sólo nos queda animar la transición entre estos dos estados.

Para realizar una transición necesitamos un estado inicial y otro final. Ya hemos puesto las propiedades del estado final, pero a esas mismas propiedades tenemos que darles un valor inicial. Es decir, si la segunda línea tiene un opacity: 0 en su estado final, en su estado inicial tiene que tener opacity: 1, para que haya una transición. Lo más probable es que el elemento ya tenga ese valor por defecto gracias al CSS del navegador, pero no está de más asegurarnos de que es así, y añadirlo nosotros:


.menu-activador-linea {
    opacity: 1;
    transform: translateY(0) rotate(0);
    transition: all 0.3s;
}

Hemos añadido también la propiedad transition, que es la que animará nuestras líneas entre el estado inicial y el final. Con esto tenemos listo nuestro icono animado.

See the Pen Menú hamburguesa animado by mariogl (@mariogl) on CodePen.

Mejoras

Hemos visto una manera de maquetar y animar el icono, pero no es la única. Por un lado podríamos hacer otras animaciones, y por otro lado, podríamos montar el sistema sin ni una sola línea de JavaScript. Os dejo un CodePen ya montado para que lo bicheéis un poco y os doy algunas pistas sobre cuál es el truco:

  • Sustituir el a por un label
  • Colocar un input checkbox oculto
  • Utilizar la pseudoclase :checked

See the Pen Menú hamburguesa animado (sólo CSS) by mariogl (@mariogl) on CodePen.

Conclusión

Hemos visto cómo jugando con el CSS podemos hacer iconos animados fácilmente, con y sin JavaScript. A partir de aquí, la imaginación es vuestra, pero usadla con cautela, por favor.

Comentarios
¿Qué opinas?

Tu dirección de correo electrónico no será publicada. * Campos obligatorios