Apuntes DAM
Volver al inicio

Multimedia web: picture, vídeo con subtítulos y una lista de reproducción

Ejercicio de HTML y CSSMedioUnos 70 minutos

Arregla la página de una jornada de puertas abiertas: picture con AVIF y WebP, imágenes con texto alternativo, tamaño y carga diferida, vídeo con controles, póster, varias fuentes y subtítulos, audio sin descarga previa, un CSS que no deja salirse nada y una lista de reproducción con JavaScript.

  • picture y formatos AVIF y WebP
  • Texto alternativo, width, height y loading=lazy
  • video, source y track (subtítulos)
  • audio y preload
  • aspect-ratio y object-fit
  • API de medios: src y load

Enunciado

Las imágenes y los vídeos son lo que más pesa de una web y donde más fallos de accesibilidad hay: imágenes sin texto alternativo, vídeos sin subtítulos ni controles, páginas que saltan al cargar porque las imágenes no dicen cuánto miden y medios que se salen de la pantalla del móvil.

La página de la jornada de puertas abiertas tiene todos esos fallos. En este ejercicio la arreglas en tres pasos: el HTML de los medios, el CSS que los hace adaptables y una pequeña lista de reproducción con JavaScript. Los ficheros de imagen, vídeo y audio no existen de verdad: las pruebas comprueban el código, no los ficheros.

Qué hay que hacer

  1. Cada apartado se corrige por separado con pruebas que se ejecutan dentro de la página (algunas a varios anchos de pantalla). Debajo del editor tienes el resultado en directo.
  2. Conserva las rutas de los ficheros (img/…, video/…, audio/…), las clases y los textos visibles: las pruebas los buscan.
  3. Los apartados se encadenan: el segundo y el tercero parten del HTML (y el tercero del CSS) ya corregidos.

Guía paso a paso

Intenta resolverlo por tu cuenta y abre un paso solo cuando te atasques: cada uno te acerca a la solución sin dártela entera.

1. Cada medio con su alternativa

Una imagen tiene su alt; un vídeo, subtítulos y un enlace de descarga dentro; un audio, su enlace (y, mejor aún, una transcripción). Si el medio no se puede ver u oír, la información tiene que llegar igual.

css
<video controls poster="img/poster.jpg">
  <source src="video/jornada.webm" type="video/webm">
  <source src="video/jornada.mp4" type="video/mp4">
  <track kind="subtitles" src="video/jornada.es.vtt" srclang="es" label="Español" default>
</video>
2. Formatos y peso

AVIF y WebP pesan bastante menos que JPEG con la misma calidad. picture deja que cada navegador elija el mejor que entienda; preload y loading deciden cuándo se descarga cada cosa.

3. Que nada salte ni se salga

Con width y height en el HTML y height: auto en el CSS, el navegador reserva el hueco exacto antes de que llegue la imagen. Es una de las métricas de Core Web Vitals: el desplazamiento acumulado del diseño (CLS).

4. La API de medios

El elemento video es un objeto con propiedades y métodos: src, load(), play(), pause(), currentTime. Una lista de reproducción solo cambia el src y vuelve a cargar.

Resuélvelo aquí

Cada apartado edita un fichero de la página (el HTML, el CSS o el JavaScript) y se corrige con pruebas que se ejecutan en la propia página, también a distintos anchos de pantalla. Debajo del editor ves el resultado en directo.

HTMLApartado 1 · HTMLMedio

Rehaz el HTML de los medios. La imagen de portada, en un figure con clase portada y su figcaption («El aula de informática, recién renovada.»), dentro de un picture con dos source (img/aula.avif de tipo image/avif y img/aula.webp de tipo image/webp, en ese orden) y la img de img/aula.jpg con el texto alternativo «Alumnos programando en el aula de informática», width="1200" y height="675". El vídeo, dentro de un div con clase video: con controles, preload="metadata", póster img/poster.jpg, width="1280" y height="720", dos source (video/jornada.webm y video/jornada.mp4, con sus tipos), una pista de subtítulos video/jornada.es.vtt en español (srclang="es", label="Español", activa por defecto) y un enlace de descarga dentro por si el navegador no puede reproducirlo. El audio, con controles, preload="none" y un enlace de descarga dentro. Las tres imágenes de la galería, con texto alternativo («Taller de electrónica», «Robot del equipo de robótica» y «Sala de estudio de la biblioteca»), width="600", height="400" y carga diferida.

estilos.csssolo lectura
index.html
Vista previa
CSSApartado 2 · CSSMedio

Hazlo adaptable con CSS: ninguna imagen ni vídeo puede salirse de su contenedor (max-width: 100%, height: auto y display: block); el div.video, siempre con proporción 16/9 y fondo negro, y el vídeo dentro ocupándolo entero con object-fit: contain; el audio a todo el ancho; el figure de la portada sin márgenes; y la galería, una Grid de tres columnas iguales con 8px de separación y miniaturas cuadradas (aspect-ratio: 1) que recorten la imagen sin deformarla y con esquinas de 8px.

index.htmlsolo lectura
estilos.css
Vista previa
JS · DOMApartado 3 · JavaScriptMedio

Añade la lista de reproducción (el HTML ya tiene la lista .lista con un botón por vídeo, su ruta en data-src, y el párrafo .reproduciendo). Al pulsar un botón: el vídeo se queda solo con esa ruta como src (quita los source de la portada) y se carga con load(); el botón pasa a tener aria-current="true" y los demás lo pierden; y el párrafo dice Reproduciendo: y el título del botón. Además, con el teclado, n (o N) pasa al vídeo siguiente y p (o P) al anterior, dando la vuelta al llegar a los extremos.

index.htmlsolo lectura
script.js
Vista previa

Solución explicada

Ver las soluciones de todos los apartados

Apartado 1 · HTML

markup
<main class="pagina">
  <h1>Jornada de puertas abiertas</h1>
  <figure class="portada">
    <picture>
      <source srcset="img/aula.avif" type="image/avif">
      <source srcset="img/aula.webp" type="image/webp">
      <img src="img/aula.jpg" alt="Alumnos programando en el aula de informática" width="1200" height="675">
    </picture>
    <figcaption>El aula de informática, recién renovada.</figcaption>
  </figure>
  <p>Te enseñamos los talleres, las aulas y los proyectos de los alumnos.</p>
  <div class="video">
    <video controls preload="metadata" poster="img/poster.jpg" width="1280" height="720">
      <source src="video/jornada.webm" type="video/webm">
      <source src="video/jornada.mp4" type="video/mp4">
      <track kind="subtitles" src="video/jornada.es.vtt" srclang="es" label="Español" default>
      <a href="video/jornada.mp4">Descarga el vídeo de la jornada</a>
    </video>
  </div>
  <h2>Escucha el podcast</h2>
  <audio controls preload="none" src="audio/podcast.mp3">
    <a href="audio/podcast.mp3">Descarga el podcast</a>
  </audio>
  <h2>Galería</h2>
  <div class="galeria">
    <img src="img/taller.jpg" alt="Taller de electrónica" width="600" height="400" loading="lazy">
    <img src="img/robotica.jpg" alt="Robot del equipo de robótica" width="600" height="400" loading="lazy">
    <img src="img/biblioteca.jpg" alt="Sala de estudio de la biblioteca" width="600" height="400" loading="lazy">
  </div>
</main>

Apartado 2 · CSS

css
* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; }
.pagina { max-width: 960px; margin: 0 auto; padding: 16px; }

/* Ningún medio se sale de su contenedor y las imágenes guardan su proporción */
img, video { display: block; max-width: 100%; height: auto; }
.portada { margin: 0; }
.portada figcaption { font-size: 0.875rem; color: #4b5563; margin-top: 4px; }

/* El vídeo siempre 16:9 */
.video { aspect-ratio: 16 / 9; background: #000; }
.video video { width: 100%; height: 100%; object-fit: contain; }

audio { width: 100%; }

/* Galería de miniaturas cuadradas */
.galeria { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.galeria img { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 8px; }

Apartado 3 · JavaScript

javascript
const video = document.querySelector('.video video');
const titulo = document.querySelector('.reproduciendo');
const botones = document.querySelectorAll('.lista button');

function elegir(boton) {
  // quitar las fuentes de la portada: el vídeo pasa a tener un único src
  video.querySelectorAll('source').forEach((s) => s.remove());
  video.src = boton.dataset.src;
  video.load();
  botones.forEach((b) => b.removeAttribute('aria-current'));
  boton.setAttribute('aria-current', 'true');
  titulo.textContent = 'Reproduciendo: ' + boton.textContent;
}

botones.forEach((b) => b.addEventListener('click', () => elegir(b)));

// Atajos de teclado del reproductor: N siguiente vídeo, P anterior
document.addEventListener('keydown', (e) => {
  if (e.target instanceof Element && e.target.closest('input, textarea')) return;
  const lista = [...botones];
  const actual = lista.findIndex((b) => b.getAttribute('aria-current') === 'true');
  if (e.key === 'n' || e.key === 'N') elegir(lista[(actual + 1) % lista.length]);
  if (e.key === 'p' || e.key === 'P') elegir(lista[(actual - 1 + lista.length) % lista.length]);
});

El texto alternativo describe la información de la imagen, no la imagen: «Alumnos programando en el aula de informática» y no «foto». Las imágenes decorativas llevan alt="" para que los lectores de pantalla las salten.

preload="metadata" descarga solo la duración y el tamaño del vídeo, y preload="none" nada hasta que se pulsa: en una página con varios medios, ahorra muchos megas a quien no los reproduce.

Los subtítulos (kind="subtitles") traducen o transcriben el diálogo; para personas sordas son mejores los captions, que incluyen también los sonidos. Los dos se escriben en ficheros WebVTT (.vtt).

aspect-ratio y object-fit sustituyen a los trucos antiguos de rellenos en porcentaje: el contenedor tiene la proporción y el medio se encaja dentro, recortado (cover) o completo (contain).

Para ir más allá

  • Añade srcset y sizes a la imagen de portada para que el móvil descargue una versión de 600px.
  • Escribe el fichero jornada.es.vtt con tres subtítulos y sus tiempos.
  • Haz unos controles propios para el vídeo (reproducir, pausa y barra de progreso) con la API de medios.

Dónde se explica