Clase 29 · Elementos dinámicos avanzados
Cinco elementos interactivos que se construyen solo con HTML y CSS: ventana popup, tabs, menú emergente, carrusel de imágenes y acordeón. Cada uno termina con el resultado en funcionamiento.
PDF · Popup PDF · Tabs PDF · Menú emergente PDF · Carrusel PDF · Acordeón
Ventana modal (Popup)
Una ventana oculta que aparece por delante de la web cuando presionamos un botón.
Vamos a construirla sin JavaScript, solo con HTML y CSS, apoyándonos en la pseudoclase :target. Los pasos son:
- Crear el botón que abre el popup.
- Crear la ventana emergente (un
div). - Ocultarla con CSS y mostrarla con
:target.
1. Botón y ventana emergente (HTML)
Dos piezas: el enlace que abre y el div que se muestra.
#popup1: tanto el botón (en su href) como el popup (en su id) tienen que tener el mismo ID.
- El primer
div id="popup1"es la capa que se superpone a la web con un color semitransparente; estará por detrás de la ventana. - El segundo
dives la ventana modal que creamos.
<a class="open-btn" href="#popup1">open</a>
<!-- POPUP -->
<div id="popup1">
<div>
<a href="#">close</a>
<h4><strong>titulo</strong></h4>
<p>aquí mi contenido...</p>
</div>
</div>
2. La capa .overlay
Definimos el estilo de la capa semitransparente que cubre la web.
Position: fixed
Necesitamos que sea fixed para que no se mueva con el scroll; ponemos todos los parámetros de posición a 0 (top, bottom, left, right).
Background semitransparente (rgba) para que veamos un poco la web que se esconde por detrás de esta capa.
Visibility: hidden hace que la capa no sea visible.
.overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.7);
transition: 0.5s;
visibility: hidden;
opacity: 0;
}
<a class="open-btn" href="#popup1">open</a>
<!-- POPUP -->
<div id="popup1" class="overlay">
<div>
<a href="#">close</a>
<h4><strong>titulo</strong></h4>
<p>aquí mi contenido...</p>
</div>
</div>
Pseudoclase :target
Ejecuta el código de la pseudoclase cuando el ID de la barra del navegador coincide con el ID del elemento.
Nos permite que se ejecute el código de la pseudoclase cuando, en la barra del navegador, coincide el ID con el ID del elemento HTML que tiene la clase principal.
- Al pulsar el botón le decimos que vaya al id
#popup1y este se carga en la barra de navegación. - Cuando coincide el ID del elemento HTML y el del navegador, se ejecuta la pseudoclase
.overlay:target.
!!! .overlay no tiene visibilidad, pero como cargamos el código de .overlay:target se la asignamos cuando pulsamos el botón.
<a class="open-btn" href="#popup1">open</a>
<div id="popup1" class="overlay">
...
</div>
.overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.7);
transition: 0.5s;
visibility: hidden;
opacity: 0;
}
.overlay:target {
visibility: visible;
opacity: 1;
}
Transición entre hidden y visible
Por qué usamos opacity además de visibility.
visibility: hidden hace que el objeto no sea visible. Hacer una transición entre el estado hidden y el estado visible no es posible; por eso ponemos opacity: 0 y opacity: 1, porque el parámetro opacity sí nos permite hacer una transición.
.overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.7);
transition: 0.5s;
visibility: hidden;
opacity: 0;
}
.overlay:target {
visibility: visible;
opacity: 1;
}
Ventana popup y botón close
Damos tamaño, color y estilo a la ventana y colocamos el botón de cerrar.
Ventana popup: definimos tamaño, color y demás estilos.
- Truco: con
margin: 150px autocentramos la ventana (también podemos usar Flex). - Añadimos una transición de 0,5s.
position: relativeporque luego el botón de cerrar lo pondremos de modo absoluto.
Botón close: colocamos el botón de forma absoluta y le damos estilo al link (color, tamaño…).
En el parámetro href del enlace ponemos # o cualquier ID diferente.
!!! Como # es diferente de #popup1, los valores de la pseudoclase .overlay:target desaparecen y se vuelve a los valores iniciales de la clase .overlay, con visibility: hidden.
.popup {
margin: 150px auto;
padding: 20px;
background: #fff;
border-radius: 5px;
width: 30%;
max-height: 30%;
position: relative;
transition: 0.5s;
}
.popup h4 {
margin-top: 0;
color: #333;
font-family: Tahoma, Arial, sans-serif;
}
.close {
position: absolute;
top: 20px;
right: 30px;
font-size: 16px;
font-weight: bold;
text-decoration: none;
color: #000000;
}
.close:hover {
color: #d80606;
}
<!-- POPUP -->
<div id="popup1" class="overlay">
<div class="popup">
<a class="close" href="#">close</a>
<h4>titulo</h4>
<p>aquí mi contenido...</p>
</div>
</div>
Animación independiente para la ventana
¿Cómo añadimos una transición solo a la ventana?
Cuando cargamos la ventana, aparece con la transición de opacidad que hemos creado entre .overlay y .overlay:target. Como en el HTML el div de la ventana está dentro del overlay (el overlay es el padre), la transición afecta a todo el contenido: el overlay y la ventana.
Ahora tenemos que crear una transición solo para la ventana, y esta transición se ha de efectuar cuando la ventana esté visible. Usamos selectores descendientes:
.overlay:target .popup { código transición }
Este código solo afectará a la ventana, moviéndola verticalmente.
.overlay:target .popup {
transition: 0.5s;
transform: translateY(150px);
}
Popup en funcionamiento
Todo el código junto y el resultado: pulsa open.
<a class="open-btn" href="#popup1">open</a>
<!-- POPUP -->
<div id="popup1" class="overlay">
<div class="popup">
<a class="close" href="#">close</a>
<h4>titulo</h4>
<p>aquí mi contenido...</p>
</div>
</div>
<!-- end POPUP -->
.overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.7);
transition: 0.5s;
visibility: hidden;
opacity: 0;
}
.overlay:target {
visibility: visible;
opacity: 1;
}
.popup {
margin: 150px auto;
padding: 20px;
background: #fff;
border-radius: 5px;
width: 30%;
max-height: 30%;
position: relative;
transition: 0.5s;
}
.popup h4 {
margin-top: 0;
color: #333;
font-family: Tahoma, Arial, sans-serif;
}
.close {
position: absolute;
top: 20px;
right: 30px;
font-size: 16px;
font-weight: bold;
text-decoration: none;
color: #000000;
}
.close:hover {
color: #d80606;
}
.overlay:target .popup {
transition: 0.5s;
transform: translateY(150px);
}
Tabs (pestañas)
Ver y ocultar secciones con botones, igual que el popup: mismo ID en el enlace y en la sección.
Las pestañas funcionan con la misma idea que el popup: cada botón apunta a un ID y la sección con ese ID se muestra gracias a :target.
HTML
Tres botones y tres secciones independientes.
Creamos 3 botones (dentro de un div para darles margen inferior) y 3 secciones independientes.
.tab1–.tab3: añadimos clases a las secciones para darles tamaño, color de fondo y espaciado.
!!! El ID del href del botón y el ID de las secciones deben coincidir.
<!-- BOTONES -->
<div style="margin-bottom:24px;">
<a class="open-btn" href="#info1">tab1</a>
<a class="open-btn" href="#info2">tab2</a>
<a class="open-btn" href="#info3">tab3</a>
</div>
<!-- TAB 1 -->
<div id="info1" class="tab1">
Info TAB1
</div>
<!-- TAB 2 -->
<div id="info2" class="tab2">
Info TAB2
</div>
<!-- TAB 3 -->
<div id="info3" class="tab3">
Info TAB3
</div>
CSS
Ocultamos con display: none y mostramos con display: block.
Para ver/ocultar vamos a utilizar:
display: none;→ ocultar.display: block;→ visualizar.
En la clase .tab1 ocultamos mediante display: none;. En la pseudoclase .tab1:target damos visibilidad mediante display: block;.
!!! Utilizamos display en lugar de visibility: hidden porque display: none no ocupa espacio; visibility: hidden oculta el contenido, pero el espacio que deja se queda en blanco.
.tab1 {
width: 750px;
height: 400px;
background-color: aqua;
border-radius: 12px;
padding: 32px;
display: none;
}
.tab1:target {
display: block;
}
Animar tabs
Con display no hay transición posible: usamos @keyframes.
Cuando utilizamos display para visualizar/ocultar, para animar las tabs tenemos que recurrir a una animación mediante @keyframes. Añadimos la clase animacion a la sección:
<!-- TAB 1 -->
<div id="info1" class="tab1 animacion">
Info TAB1
</div>
@keyframes anim {
0% {
transform: translateY(50px);
}
100% {
transform: translateY(0px);
}
}
.animacion {
animation: anim;
animation-duration: 2s;
animation-iteration-count: 1;
}
Tabs en funcionamiento
Código completo con las tres pestañas: pulsa tab1, tab2 o tab3.
En el código completo agrupamos .tab1, .tab2, .tab3 para no repetir los estilos comunes y dejamos solo el color de fondo en cada una.
<!-- BOTONES -->
<div style="margin-bottom:24px;">
<a class="open-btn" href="#info1">tab1</a>
<a class="open-btn" href="#info2">tab2</a>
<a class="open-btn" href="#info3">tab3</a>
</div>
<!-- TAB 1 -->
<div id="info1" class="tab1 animacion">
Info TAB1
</div>
<!-- TAB 2 -->
<div id="info2" class="tab2 animacion">
Info TAB2
</div>
<!-- TAB 3 -->
<div id="info3" class="tab3 animacion">
Info TAB3
</div>
.tab1, .tab2, .tab3 {
width: 750px;
height: 400px;
border-radius: 12px;
padding: 32px;
display: none;
}
.tab1 {
background-color: aqua;
}
.tab2 {
background-color: #ffd9a0;
}
.tab3 {
background-color: #f8c1de;
}
.tab1:target, .tab2:target, .tab3:target {
display: block;
}
@keyframes anim {
0% {
transform: translateY(50px);
}
100% {
transform: translateY(0px);
}
}
.animacion {
animation: anim;
animation-duration: 2s;
animation-iteration-count: 1;
}
Carrusel (slider de imágenes)
Un carrusel sin JavaScript: una galería en fila, un contenedor que oculta lo que sobra y enlaces que se desplazan a cada imagen.
Vamos a construirlo en nueve pasos numerados. La idea: colocamos las imágenes una al lado de otra, mostramos solo una (con overflow: hidden) y los puntos de navegación son enlaces a los ID de cada imagen.
Contenedor y galería
Pasos 1, 2 y 3.
1. Contenedor: creamos el contenedor padre container, le damos un tamaño fijo (600×400) y le asignamos un borde para poder visualizarlo.
2. Galería: creamos el contenedor gallery, que es el que llevará las imágenes (con display: flex).
3. Las imágenes tienen que tener el mismo tamaño que el contenedor principal.
<div class="container">
<div class="gallery">
<img src="img1.svg">
<img src="img2.svg">
<img src="img3.svg">
<img src="img4.svg">
</div>
</div>
.container {
border: 1px solid black;
width: 600px;
height: 400px;
}
.gallery {
display: flex;
}
.gallery img {
width: 600px;
height: 400px;
}
Ocultar el contenido desbordado e ID de las imágenes
Pasos 4 y 5.
4. Ocultar el contenido desbordado: ocultamos lo que se sale del contenedor principal con overflow: hidden;.
5. ID de las imágenes: asignamos un id diferente a cada imagen. Nos servirá para poder desplazarnos entre las imágenes.
<div class="container">
<div class="gallery">
<img id="img1" src="img1.svg">
<img id="img2" src="img2.svg">
<img id="img3" src="img3.svg">
<img id="img4" src="img4.svg">
</div>
</div>
.container {
border: 4px solid black;
width: 600px;
height: 400px;
overflow: hidden;
}
Contenedor de dots y dots de navegación
Pasos 6 y 7.
6. Contenedor de dots: creamos un contenedor para los «dots» de navegación. Este contenedor es flex para alinear horizontalmente los puntos.
7. Dots: cada dot es una etiqueta <a>; para dar la forma de círculo damos en CSS la misma anchura y altura, asignamos un color de fondo y border-radius para hacerlo circular.
IMPORTANTE: el ID de la imagen tiene que coincidir con el href del enlace (dot).
<div id="dots">
<a class="dot" href="#img1"></a>
<a class="dot" href="#img2"></a>
<a class="dot" href="#img3"></a>
<a class="dot" href="#img4"></a>
</div>
#dots {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
margin-top: 16px;
}
.dot {
width: 15px;
height: 15px;
border-radius: 50px;
background-color: rgb(169, 169, 169);
}
Centrar el carrusel y los dots
Paso 8.
8. Para centrar verticalmente el carrusel y los dots creamos un contenedor superior que lo englobe todo y centramos con flex columna.
<div class="column">
<div class="container">
<div class="gallery">
<img id="img1" src="img1.svg">
<img id="img2" src="img2.svg">
<img id="img3" src="img3.svg">
<img id="img4" src="img4.svg">
</div>
</div>
<div id="dots">
<a class="dot" href="#img1"></a>
<a class="dot" href="#img2"></a>
<a class="dot" href="#img3"></a>
<a class="dot" href="#img4"></a>
</div>
</div>
.column {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10px;
}
Suavizar el movimiento del scroll horizontal
Paso 9.
9. El carrusel funciona, pero el cambio de imágenes lo hace de golpe. Para suavizarlo aplicamos scroll-behavior: smooth; en el contenedor padre.
.container {
border: 4px solid black;
width: 600px;
height: 400px;
overflow: hidden;
scroll-behavior: smooth;
}
Carrusel en funcionamiento
Código completo y resultado: pulsa los puntos.
En el ejemplo las imágenes son ilustraciones img1.svg–img4.svg; en tu ejercicio usa las tuyas (.jpg) con el mismo tamaño que el contenedor.
<div class="column">
<div class="container">
<div class="gallery">
<img id="img1" src="img1.svg">
<img id="img2" src="img2.svg">
<img id="img3" src="img3.svg">
<img id="img4" src="img4.svg">
</div>
</div>
<div id="dots">
<a class="dot" href="#img1"></a>
<a class="dot" href="#img2"></a>
<a class="dot" href="#img3"></a>
<a class="dot" href="#img4"></a>
</div>
</div>
.container {
border: 4px solid black;
width: 600px;
height: 400px;
overflow: hidden;
scroll-behavior: smooth;
}
.gallery {
display: flex;
}
.gallery img {
width: 600px;
height: 400px;
}
#dots {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
margin-top: 16px;
}
.dot {
width: 15px;
height: 15px;
border-radius: 50px;
background-color: rgb(169, 169, 169);
}
.column {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 10px;
}
Acordeón
Secciones que se abren y se cierran al hacer clic, con HTML puro.
El acordeón no necesita :target ni JavaScript: el navegador ya trae las etiquetas <details> y <summary>.
<details> y <summary>
Las dos etiquetas de un acordeón.
<details>: etiqueta que hace referencia al elemento del acordeón; abre y cierra al hacer clic.
<summary>: hace referencia al título o la sección que se ve cuando el acordeón está cerrado.
<details>
<summary>Título cabecera</summary>
Información dentro del acordeon
</details>
Estado inicial del acordeón
Por defecto todos los <details> están cerrados.
Por defecto todos los elementos <details> están cerrados, pero podemos querer que uno en concreto esté abierto desde el inicio. Para ello simplemente ponemos el parámetro open dentro de la etiqueta: <details open>.
<details open>
<summary>Título cabecera</summary>
Información dentro del acordeon
</details>
<details>
<summary>Título cabecera</summary>
Información dentro del acordeon
</details>
Acordeón desasociado y asociado
Con name abrimos uno y se cierran los demás.
Acordeón desasociado: cada <details> funciona de forma independiente; si tengo 2, se abren y cierran sin tener en cuenta lo que hace el otro.
Acordeón asociado: si queremos que, al abrir un <details>, se cierren el resto, tenemos que asociarlos. Para ello introducimos el parámetro name="nombre". Todos los <details> tienen que tener el mismo nombre para que estén asociados.
<details open>
<summary>Título cabecera</summary>
Información dentro del acordeon
</details>
<details>
<summary>Título cabecera</summary>
Información dentro del acordeon
</details>
<details open name="acordeon">
<summary>Título cabecera</summary>
Información dentro del acordeon
</details>
<details name="acordeon">
<summary>Título cabecera</summary>
Información dentro del acordeon
</details>
CSS del acordeón
Estilo para details y para details summary.
details: añadimos tamaño, color de fondo…
details summary: añadimos estilo al texto del summary.
!! margin-bottom: 16px; genera una separación respecto al siguiente details.
details {
background-color: #f6f8fa;
width: 80%;
margin-bottom: 16px;
border-radius: 8px;
border: 1px solid #d8e0e9;
color: #364f6b;
padding: 16px;
}
details summary {
font-weight: 400;
font-size: 18px;
padding: 1rem;
}
CSS cuando details está «open»
Funciona igual que un :hover.
Podemos asignar un CSS diferente cuando details y summary están abiertos (del mismo modo que cuando hacemos un hover):
details[open]: añadimos CSS diferente cuando está abierto (otro color de fondo, una sombra…).
details[open] summary: añadimos CSS diferente a summary cuando está abierto.
details[open] {
background: #ffffff;
}
details[open] summary {
font-weight: 600;
}
Personalizar el icono desplegable
Ocultamos el triángulo por defecto y ponemos el nuestro con ::before.
- Asignar
position: relativeadetails; después pondremos el icono en modo absoluto. - Ocultar el icono que viene por defecto con
list-style: none;. - Mediante un pseudoelemento
details summary::before: cambiamos el icono encontent, el tamaño mediantefont-size, la posiciónabsolutey unatransitionpara generar movimiento. - Giramos el icono cuando
detailsestá abierto.
4.2 Como alternativa podemos poner otro icono diferente cuando esté abierto, en el content de ::before. El símbolo que ponemos en content lo copiamos de una página de símbolos, por ejemplo copychar.cc.
En Safari el triángulo se oculta con summary::-webkit-details-marker { display: none; }.
details {
background-color: #f6f8fa;
width: 80%;
margin-bottom: 1rem;
border-radius: 8px;
border: 1px solid #d8e0e9;
color: #364f6b;
padding: 16px;
position: relative;
}
details summary {
font-weight: 800;
font-size: 18px;
padding: 1rem;
list-style: none;
}
details summary::before {
position: absolute;
content: "👇";
font-size: 1.75rem;
top: 10px;
right: 16px;
transition: 0.3s;
}
details[open] summary::before {
transform: rotate(45deg);
transition: 0.3s;
}
details[open] summary::before {
content: "👆";
font-size: 24px;
}
Icono desde un archivo (método alternativo)
Cargamos la imagen con url() en lugar de pegar un símbolo.
Si queremos cargar un icono desde nuestro ordenador en lugar de pegar un símbolo, cargamos dentro de content la URL de la imagen/icono y le damos tamaño mediante width en lugar de font-size.
details summary::before {
position: absolute;
content: url("arrow.svg");
width: 30px;
right: 10px;
top: 15px;
transition: 0.3s;
}
details[open] summary::before {
transform: rotate(90deg);
transition: 0.3s;
}
Acordeón en funcionamiento
Código completo y resultado: abre una sección y verás cómo se cierra la otra.
El ejemplo usa el acordeón asociado (name="acordeon") con el icono cargado desde arrow.svg.
<details open name="acordeon">
<summary>Ver más información</summary>
<h3>Nombre del sujeto</h3>
<img src="img1.svg" width="120" alt="">
</details>
<details name="acordeon">
<summary>Ver más información</summary>
Información dentro del acordeon
</details>
<details name="acordeon">
<summary>Ver más información</summary>
Información dentro del acordeon
</details>
details {
background-color: #f6f8fa;
width: 80%;
margin-bottom: 1rem;
border-radius: 8px;
border: 1px solid #d8e0e9;
color: #364f6b;
padding: 16px;
position: relative;
}
details summary {
font-weight: 800;
font-size: 18px;
padding: 1rem;
list-style: none;
}
details[open] {
background: #ffffff;
}
details[open] summary {
font-weight: 600;
}
details summary::before {
position: absolute;
content: url("arrow.svg");
width: 30px;
right: 10px;
top: 15px;
transition: 0.3s;
}
details[open] summary::before {
transform: rotate(90deg);
transition: 0.3s;
}