Multimedia web: picture, vídeo con subtítulos y una lista de reproducción
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
- 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.
- Conserva las rutas de los ficheros (
img/…,video/…,audio/…), las clases y los textos visibles: las pruebas los buscan. - 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.
<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.
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.
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.
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.
Solución explicada
Ver las soluciones de todos los apartados
Apartado 1 · HTML
<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
* { 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
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
srcsetysizesa la imagen de portada para que el móvil descargue una versión de 600px. - Escribe el fichero
jornada.es.vttcon 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.