2023-11-11
Menú HTML invisible con cabecera y pie de página.
Dando vueltas y mirando ejemplos por la red, no encontraba un ejemplo simple para crear un menú lateral, que se abrirá al pulsar un botón teniendo una cabecera y pie de páginas fijos, así que tras mucho investigar y dar vueltas, encontré una solución simple usando CSS y Javascript.
El cuerpo HTML
<body> <header> <span class="botonMenu" onclick="toggleMenu()">☰</span> <span class="cabecera">header</span> <span class="botonVacio"></span> </header> <footer> <span>footer</span> </footer> <div class="contenedor"> <!-- CóDIGO HTML --> </div> <nav id="idnav"> <a href="">LINK 1</a> <a href="">LINK 2</a> <a href="">LINK 3</a> <a href="">LINK 4</a> </nav> </body>
La estructura es muy simple: una cabecera, un pie de página, un contenedor y un menú.
Dentro de la cabecera se ha creado tres span que se colocarán mediante CSS.
Uno de ellos se corresponde con el botón que en vez de usar una imagen he optado por usar el código HTML de &9776 que son tres rayas verticales asemejándose a un botón de menú. Se puede cambiar por una imagen o por un carácter especial de alguna fuente de iconos. Se le ha añadido el evento onclick para ejecutar una función Java cuando este sea pulsado.
El span central sirve para colocar el título de nuestro sitio o cualquier cosa, mientras que el lateral derecho lo he dejado en blanco aunque se puede usar, por ejemplo, para una foto de perfil o similar.
Dentro del footer solo he colocado un texto y dentro del div de contenido no he colocado nada puesto que es un ejemplo.
El nav será nuestro menú. He colocado dentro varios enlaces para que parezca un poco mas un menú que dejándolo vacío. A este nav le he puesto un identificador "idnav" para luego usarlo en la función java.
CSS
Ahora añadiremos nuestro estilo con CSS.
header {
position: fixed;
width: 100vw;
height: 50px;
background-color: black;
color: white;
display: flex;
align-items: center;
justify-content: space-between;
}
La cabecera tendrá una posición fija y su ancho será 100vw, es decir, el 100% de la pantalla. Le he dado un alto fijo en pixeles que utilizaremos después.
Para ordenar los elementos he usado flex, haciendo que los elementos se centren en vertical y espaciados entre ellos, haciendo que el botón quede a la izquierda, el titulo centrado y el span vacío a la derecha.
.botonMenu {
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
.botonMenu:hover {
background-color: white;
color: black;
}
.botonVacio {
width: 50px;
height: 50px;
}
Al span del botón le he dado un tamaño de 50x50, justo lo que es el alto que le dimos a la cabecera. Tambíén he usado flex para colocar el contenido centrado y, por último, he cambiado el color de fondo y de la letra cuando se pasa el ratón por encima con el modificador hover.
De igual manera al span vacío le he dado forma con un tamaño igual que el botón de 50x50.
footer {
width: 100vw;
height: 50px;
position: fixed;
bottom: 0;
background-color: black;
color: white;
display: flex;
align-items: center;
justify-content: center;
}
Al pie de pagina le he dado el mismo tamaño que a la cabecera y también le he indicado que su posición es fija, pero con la salvedad de que lo que colocado abajo de la página con bottom siendo 0.
Se ha usado flex para mostrar el texto centrado y se le ha dado color de texto y fondo.
.contenedor {
position: fixed;
width: 100vw;
height: calc( 100vh - 100px );
top: 50px;
overflow: scroll;
padding: 10px;
}
Al div que va a contener nuestra página le he dado un ancho de 100vw pero su altura hay que calcularla. Para ellos usamos la función CSS calc(), indicando el 100vw (el alto de lo que vemos) menos el alto de la cabecera y el píe de página.
Le indicamos que haga un scroll del contenido si este se desborda y le damos un pequeño padding para separar el contenido de los bordes.
nav {
position: fixed;
top: 50px;
left: -100vw;
width: 100vw;
height: calc( 100vh - 100px );
background-color: black;
color: white;
transition: left 0.5s ease;
}
nav.activo {
left: 0;
transition: left 0.5s ease;
}
nav a {
display: flex;
width: 100%;
height: 50px;
text-decoration: none;
color: white;
align-items: center;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
nav a:before {
content: " ";
padding-right: 1em;
}
nav a:hover {
background-color: white;
color: black;
}
Ahora empieza la magia... Cuando le damos estilo al menú le daremos un valor de ancho de toda la pantalla (100vw) y su alto será igual al alto de la pantalla menos el alto de la cabecera y el pie de página. Usamos la función calc para ello.
Le asignamos su posición fija, pero su parte superior (top) será donde acaba el alto de la cabecera y su parte izquierda (propiedad left) será de -100vw, haciendo que el menú no se vea en pantalla ya que estará todo a la izquierda.
Para hacer que se vea añadimos una clase a nav llamada activo. Si la clase se asigna al nav entonces su valor left será 0 haciendo que se vea el menú.
Se ha usado transition para crear una animación simple al ocultar/mostrar el menú
El resto de reglas CSS son para dar formato a los botones y se vean mas "chulos".
JavaScript
function toggleMenu() {
let x = document.getElementById("idnav");
x.classList.toggle("activo");
}
Como vimos con CSS, el menú se encuentra oculto con su clase "normal", pero si le damos la clase "activo" se mostrará. Tan solo hemos de hacer que cuando pulsamos el botón, cambiemos los atributos de clase de nav.
Para ello le dimos un identificador para poder obtener el objeto y usando la función classList.toggle() podemos hacer que "activo" desaparezca o aparezca en la lista de clases del objeto nav.
Conclusión
Quizás no sea muy vistoso pero es totalmente funcional. Hay que tener en cuenta que cada navegador se comporta de manera diferente. Lo he probado en Firefox y funciona correctamente.
Dejo el código completo aquí:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
footer {
width: 100vw;
height: 50px;
position: fixed;
bottom: 0;
background-color: black;
color: white;
display: flex;
align-items: center;
justify-content: center;
}
header {
position: fixed;
width: 100vw;
height: 50px;
background-color: black;
color: white;
display: flex;
align-items: center;
justify-content: space-between;
}
.botonMenu {
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
.botonMenu:hover {
background-color: white;
color: black;
}
.botonVacio {
width: 50px;
height: 50px;
}
.contenedor {
position: fixed;
width: 100vw;
height: calc( 100vh - 100px );
top: 50px;
overflow: scroll;
padding: 10px;
}
nav {
position: fixed;
top: 50px;
left: -100vw;
width: 100vw;
height: calc( 100vh - 100px );
background-color: black;
color: white;
transition: left 0.5s ease;
}
nav.activo {
left: 0;
transition: left 0.5s ease;
}
nav a {
display: flex;
width: 100%;
height: 50px;
text-decoration: none;
color: white;
align-items: center;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
nav a:before {
content: " ";
padding-right: 1em;
}
nav a:hover {
background-color: white;
color: black;
}
</style>
<script>
function toggleMenu() {
let x = document.getElementById("idnav");
x.classList.toggle("activo");
}
</script>
</head>
<body>
<header>
<span class="botonMenu" onclick="toggleMenu()">☰</span>
<span class="cabecera">header</span>
<span class="botonVacio"> </span>
</header>
<footer>
<span>footer</span>
</footer>
<div class="contenedor">
<!-- CóDIGO HTML -->
</div>
<nav id="idnav">
<a href="">LINK 1</a>
<a href="">LINK 2</a>
<a href="">LINK 3</a>
<a href="">LINK 4</a>
</nav>
</body>
</html>
Una nueva nota.
Haciendo pruebas de como se ve el menú en distintos navegadores he descubierto que en casi todos funciona bien, excepto en Google Chrome para Android. El hecho es que el contenido se "corta" al final no mostrandose todo, siendo este tapado por el footer.
Investigando un poco llegué a la conclusión de que es un problema de los dispositivos móviles que cuando calculan el ancho y el alto de la pantalla, añaden lo que es la barra de navegación del propio navegador haciendo que el contenido sea mayor y se solape con el footer.
He encontrado una solución con un hack de css para los navegadores y cambiano la unidad vh a vdh.
vh/vw son las unidades viewport height/width. Pero en los navegadores como Safari este corresponde con el tamaño de la pantalla. Mientras que el navegador puede tener una cabecera e incluso un pie de página al final.
dvh/dvw son las unidades dinamyc viewport height/width que coinciden con el área visible de la página. Este mecanismo fue añadido hace muy poco al estándar.
.contenedor {
position: fixed;
width: 100dvw;
top: 50px;
height: calc(100dvh - 100px);
/* En el móvil no se ve el contenedor entero debido a un problema con google
y apple que han decidido que el tamaño 100vh sea toda la pantalla y no
solo lo que se muestra de la página. Eso incluye la barra de dirección del
navegador chrome así que buscando por la red encontré este hack para CSS
que aplica un max-height teniendo en cuenta el alto de la cabecera y
que sea para el sistema operativo Android. */
@supports (-webkit-appearance:none) {
.os-android & {
max-height: calc(100vh - 150px);
}
}
Historial
12/01/2024: Se ha añadido una corrección del código para que se muestre correctamente en móviles.