Saltar al contenido principal
Leyendo: Componentes interactivos modernos
Apuntes de clase

Clase 31 · Componentes interactivos modernos

La versión actual de lo que vimos en la clase 29: scroll snap para carruseles, popover y dialog para ventanas y menús, transiciones de entrada y salida, y animaciones por scroll y view transitions. Cada bloque termina con su demo.

01 · Scroll snap

Scroll snap: carruseles sin ID

El contenedor «imanta» el scroll a puntos concretos.

En la clase 29 el carrusel necesitaba un id por imagen y enlaces para cambiar. Con scroll snap basta un contenedor con overflow y un par de propiedades: el usuario desliza (ratón, trackpad o dedo) y cada imagen se queda perfectamente colocada.

01 · Scroll snap

scroll-snap-type

En el contenedor: eje y comportamiento.

Necesita que el contenedor tenga overflow (auto o scroll) en ese eje.

style.css
.carrusel {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
}
valores
/* eje: x, y o both */

/* siempre se detiene en un punto */
scroll-snap-type: x mandatory;

/* solo se detiene si está cerca de un punto */
scroll-snap-type: x proximity;

/* dónde se alinea cada elemento */
scroll-snap-align: start;
scroll-snap-align: center;
scroll-snap-align: end;
01 · Scroll snap

scroll-snap-align

En cada elemento: dónde se detiene.

style.css
.slide {
  scroll-snap-align: center;
}

Con center la imagen queda centrada; con start pegada al inicio. También existe scroll-snap-stop: always para que no se salte ninguna imagen al deslizar rápido.

01 · Scroll snap

scroll-padding y scroll-snap vertical

Margen de seguridad y secciones a pantalla completa.

scroll-padding deja un margen en el contenedor (útil con cabeceras fijas). En vertical, es la base de las páginas «por pantallas».

style.css · scroll vertical
.pagina {
  height: 100vh;
  overflow-y: auto;
  scroll-snap-type: y mandatory;
}
.pagina section {
  height: 100vh;
  scroll-snap-align: start;
}
01 · Scroll snap

Scroll snap en funcionamiento

Desliza el carrusel horizontalmente con el ratón, el trackpad o el dedo.

index.html
<div class="carrusel">
  <img class="slide" src="../dinamicos/img1.svg" alt="">
  <img class="slide" src="../dinamicos/img2.svg" alt="">
  <img class="slide" src="../dinamicos/img3.svg" alt="">
  <img class="slide" src="../dinamicos/img4.svg" alt="">
</div>
style.css
.carrusel {
  display: flex;
  gap: 16px;
  padding: 16px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding: 16px;
}
.slide {
  flex: 0 0 80%;
  height: 220px;
  object-fit: cover;
  border-radius: 12px;
  scroll-snap-align: center;
}
Resultado: scroll snap
02 · Popover y dialog

Ventanas y menús con HTML nativo

El navegador ya sabe abrir, cerrar y poner capas por encima: no hace falta el truco de :target.

En la clase 29 construimos el popup y el menú con :target. Funciona, pero cambia la URL y no es accesible. HTML tiene dos soluciones nativas:

  • popover: menús, avisos y cajas emergentes.
  • <dialog>: ventanas modales con fondo oscurecido.

Los dos están soportados por los navegadores actuales. Comprueba la compatibilidad en caniuse.com antes de usarlos en producción.

02 · Popover y dialog

El atributo popover

Un botón y una caja, enlazados por el id.

  • La caja lleva el atributo popover y un id.
  • El botón lleva popovertarget="id".
  • Se cierra solo al pulsar Esc o al hacer clic fuera.
  • Mientras está abierta, podemos darle estilo con :popover-open.
index.html
<button popovertarget="menu">Abrir menú</button>

<div id="menu" popover>
  <a href="#">Inicio</a>
  <a href="#">Clases</a>
  <a href="#">Contacto</a>
</div>
style.css
#menu {
  inset: 70px auto auto 16px;
  margin: 0;
  padding: 12px;
  border: 0;
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
}
#menu a {
  display: block;
  padding: 6px 10px;
  color: #364f6b;
  text-decoration: none;
}
#menu a:hover {
  background-color: #e6f5d6;
}
#menu:popover-open {
  background-color: #fff;
}

Las cajas con popover están ocultas por defecto, se muestran sobre todo lo demás (no necesitan z-index) y vienen centradas en pantalla; por eso en el ejemplo cambiamos inset y margin.

02 · Popover y dialog

La etiqueta

Una ventana modal con fondo oscurecido y cierre con Esc.

Para abrirla como modal usamos showModal(); es la única línea de JavaScript del apartado. Un <form method="dialog"> con un botón cierra la ventana sin código.

index.html
<button onclick="ventana.showModal()">Abrir ventana</button>

<dialog id="ventana">
  <h3>Ventana modal</h3>
  <p>Se cierra con la tecla Esc o con el botón.</p>
  <form method="dialog">
    <button>Cerrar</button>
  </form>
</dialog>

El fondo oscurecido es el pseudoelemento ::backdrop y se estiliza como cualquier otro:

style.css
dialog {
  border: 0;
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
dialog::backdrop {
  background-color: rgba(0, 0, 0, 0.6);
}
02 · Popover y dialog

Popover y dialog en funcionamiento

Abre el menú y la ventana; prueba a cerrarlos con Esc.

index.html
<button class="open-btn" popovertarget="menu">Abrir menú</button>

<div id="menu" popover>
  <a href="#">Inicio</a>
  <a href="#">Clases</a>
  <a href="#">Contacto</a>
</div>

<button class="open-btn" onclick="ventana.showModal()">Abrir ventana</button>

<dialog id="ventana">
  <h3>Ventana modal</h3>
  <p>Se cierra con la tecla Esc o con el botón.</p>
  <form method="dialog">
    <button class="open-btn">Cerrar</button>
  </form>
</dialog>
style.css
#menu {
  inset: 70px auto auto 16px;
  margin: 0;
  padding: 12px;
  border: 0;
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
}
#menu a {
  display: block;
  padding: 6px 10px;
  color: #364f6b;
  text-decoration: none;
}
#menu a:hover {
  background-color: #e6f5d6;
}
#menu:popover-open {
  background-color: #fff;
}
dialog {
  border: 0;
  border-radius: 12px;
  padding: 24px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}
dialog::backdrop {
  background-color: rgba(0, 0, 0, 0.6);
}
Resultado: popover y dialog
03 · Transiciones de entrada y salida

Animar lo que aparece y desaparece

Hasta ahora, display no se podía animar.

En la clase 29 vimos que display: none no admite transición y que había que recurrir a opacity y visibility. Dos novedades de CSS lo resuelven:

  • transition-behavior: allow-discrete: permite que display (y overlay) participen en una transición.
  • @starting-style: indica el estado desde el que arranca un elemento que acaba de aparecer.
style.css · el problema
/* display no se puede animar: pasa de none a block de golpe */
.caja {
  display: none;
  transition: opacity 0.4s;
}
.caja.visible {
  display: block;
  opacity: 1;
}
03 · Transiciones de entrada y salida

allow-discrete

Hace que display se anime al final (al ocultar) o al principio (al mostrar).

Se añade a cada propiedad «discreta» en la lista de transition. Para los popover y dialog añadimos también overlay, así el elemento se queda en la capa superior mientras dura la animación de salida.

style.css
.caja {
  transition: opacity 0.4s, display 0.4s allow-discrete;
}
03 · Transiciones de entrada y salida

@starting-style

El estado «antes de aparecer».

Cuando un elemento pasa de display: none a visible, no tiene un estado anterior desde el que transicionar. Con @starting-style se lo damos.

style.css
.caja:popover-open {
  opacity: 1;
}

@starting-style {
  .caja:popover-open {
    opacity: 0;
  }
}
03 · Transiciones de entrada y salida

Entrada y salida en funcionamiento

La caja entra y sale con un fundido y un desplazamiento.

index.html
<button class="open-btn" popovertarget="caja">Mostrar</button>

<div id="caja" popover>
  <h3>Hola</h3>
  <p>Aparezco y desaparezco con animación.</p>
</div>
style.css
#caja {
  width: 260px;
  padding: 20px;
  border: 0;
  border-radius: 12px;
  background-color: #e6f5d6;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);

  /* estado de salida (cerrado) */
  opacity: 0;
  transform: translateY(30px);
  transition: opacity 0.4s, transform 0.4s, display 0.4s allow-discrete, overlay 0.4s allow-discrete;
}

/* estado abierto */
#caja:popover-open {
  opacity: 1;
  transform: translateY(0);
}

/* estado de entrada: desde dónde empieza al abrirse */
@starting-style {
  #caja:popover-open {
    opacity: 0;
    transform: translateY(30px);
  }
}
Resultado: entrada y salida
04 · Scroll y view transitions

Animaciones ligadas al scroll

La animación avanza con el scroll en lugar de con el tiempo.

En la clase 20 vimos animation-timeline: scroll(), que usa el scroll de toda la página. Con view() la animación depende de cuánto se ve el propio elemento en pantalla: es la base de los efectos «aparecer al hacer scroll».

Las animaciones por scroll y las view transitions son muy recientes. Comprueba su compatibilidad antes de usarlas y piensa un resultado aceptable para navegadores que no las soporten.

04 · Scroll y view transitions

animation-timeline: view()

Una animación normal con @keyframes y un cambio de línea de tiempo.

style.css · animación
@keyframes aparecer {
  from {
    opacity: 0;
    transform: translateY(60px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
style.css · línea de tiempo
.tarjeta {
  animation: aparecer linear both;
  animation-timeline: view();
  animation-range: entry 0% cover 40%;
}
  • animation-timeline: view(): el progreso depende de la visibilidad del elemento.
  • animation-range: entry 0% cover 40%: la animación empieza cuando el elemento empieza a entrar y termina cuando ya ha recorrido el 40% de su trayecto.
04 · Scroll y view transitions

Animación por scroll en funcionamiento

Haz scroll dentro de la ventana de la demo.

index.html
<section class="pagina">
  <div class="tarjeta">Tarjeta 1</div>
  <div class="tarjeta">Tarjeta 2</div>
  <div class="tarjeta">Tarjeta 3</div>
  <div class="tarjeta">Tarjeta 4</div>
  <div class="tarjeta">Tarjeta 5</div>
  <div class="tarjeta">Tarjeta 6</div>
</section>
style.css
@keyframes aparecer {
  from {
    opacity: 0;
    transform: translateY(60px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
.tarjeta {
  animation: aparecer linear both;
  animation-timeline: view();
  animation-range: entry 0% cover 40%;
}
.tarjeta {
  height: 140px;
  margin-bottom: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.4rem;
  font-weight: 700;
  color: #fff;
  background-color: #3a8dde;
  border-radius: 12px;
}
Resultado: animación por scroll
04 · Scroll y view transitions

View transitions entre páginas

Una transición animada al cambiar de una página a otra, sin JavaScript.

Se activa con una regla @view-transition en las dos páginas. Los elementos que tengan el mismo view-transition-name en ambas se «transforman» de uno al otro (posición, tamaño y color).

style.css · en las dos páginas
@view-transition {
  navigation: auto;
}
.caja {
  view-transition-name: caja;
}
04 · Scroll y view transitions

View transition en funcionamiento

Pulsa el enlace: la caja se transforma entre las dos páginas.

página 1
<h1>Página 1</h1>
<div class="caja"></div>
<a class="open-btn" href="transicion-pagina-2.html">Ir a la página 2</a>
estilo página 1
@view-transition {
  navigation: auto;
}
.caja {
  view-transition-name: caja;
}
.caja {
  width: 100px;
  height: 100px;
  background-color: #3a8dde;
  border-radius: 12px;
  margin-bottom: 20px;
}
página 2
<h1>Página 2</h1>
<div class="caja"></div>
<a class="open-btn" href="transicion-pagina-1.html">Volver a la página 1</a>
estilo página 2 (solo cambia la caja)
@view-transition {
  navigation: auto;
}
.caja {
  view-transition-name: caja;
}
.caja {
  width: 320px;
  height: 160px;
  background-color: #6ea832;
  border-radius: 80px;
  margin: 0 0 20px auto;
}
Resultado: view transition