preload y poster: los dos atributos que deciden cuánto pesa un vídeo antes de reproducirse
Qué descarga cada uno de los tres valores de preload, por qué el navegador puede ignorarlos, y cómo un poster mal elegido convierte un vídeo en el elemento del LCP con la peor imagen posible.
Un vídeo que nadie reproduce debería costar cero. En la práctica, un <video> sin configurar puede descargar varios megabytes antes de que el usuario haya decidido si le interesa, y una página con tres vídeos incrustados puede transferir más en cargas que nadie pidió que en el contenido entero. Los dos atributos que controlan eso son preload y poster, y los dos tienen comportamientos que no coinciden con lo que su nombre sugiere.
- Describir qué transfiere exactamente cada uno de los tres valores de
preload. - Explicar por qué el navegador puede desobedecer el atributo y en qué casos lo hace.
- Elegir y optimizar un
posterpara que no arruine el LCP. - Medir el coste real en bytes de un vídeo no reproducido.
Los tres valores y lo que descarga cada uno
El atributo preload es una pista, no una orden. Acepta tres valores.
preload="none" le dice al navegador que no descargue nada del vídeo hasta que el usuario lo pida. Ni un byte del medio. La consecuencia visible es que el elemento no conoce su duración ni sus dimensiones intrínsecas hasta que empieza a cargar, así que la barra de progreso de los controles nativos aparece vacía y el elemento colapsa a su tamaño por defecto si no le has dado width y height.
preload="metadata" descarga solo la cabecera del fichero: duración, dimensiones, pistas disponibles, y el índice de fotogramas clave. En un MP4 con faststart eso son típicamente entre 20 y 200 KB, dependiendo de la longitud del vídeo, porque el índice crece con el número de fotogramas. En un fichero sin faststart es mucho peor: el índice está al final, y el navegador tiene que hacer al menos una petición de rango al final del fichero, o en el peor caso descargarlo entero.
preload="auto" autoriza al navegador a descargar todo lo que quiera, potencialmente el fichero completo. Es la opción que hace que tres vídeos incrustados transfieran 30 MB en una página de la que el usuario ve la primera pantalla.
El valor por defecto no está fijado por la especificación: cada navegador elige el suyo, y en la práctica los de escritorio se comportan como metadata. La parte importante viene ahora.
El navegador desobedece, y con razón
En móvil, casi todos los navegadores tratan cualquier valor de preload como none para ahorrar datos del usuario, salvo que el vídeo esté marcado para reproducción automática. Es una decisión de política, no un error, y significa que preload="auto" en un móvil no hace absolutamente nada. Si tu estrategia de arranque rápido depende de que el vídeo esté precargado, en móvil no existe.
En escritorio la desobediencia también ocurre pero en la otra dirección: con conexiones rápidas y sin señal de ahorro de datos, algunos navegadores precargan más de lo que pides. Y con la reducción de datos activa, auto se degrada a metadata o a none.
Todo esto lleva a una conclusión operativa clara: preload sirve para decir «menos», no para decir «más». Ponerlo a none funciona de forma fiable en todas partes. Ponerlo a auto es una sugerencia que se ignora en la mitad de los dispositivos. Si necesitas garantizar que un vídeo esté listo, la herramienta no es preload, es <link rel="preload" as="video"> o directamente arrancar la descarga tú.
La configuración por defecto sensata para un vídeo incrustado en una página de contenido:
<video controls playsinline preload="none"
poster="portada-800.avif"
width="1280" height="720">
<source src="entrevista-av1.mp4" type='video/mp4; codecs="av01.0.05M.08"'>
<source src="entrevista-h264.mp4" type='video/mp4; codecs="avc1.640028"'>
<track kind="captions" src="entrevista-es.vtt" srclang="es" label="Español" default>
</video>
Con preload="none" y un poster, el coste del vídeo antes de que alguien pulse el botón es exactamente el peso del póster. Con width y height declarados, la caja no se mueve. Esas dos líneas son la diferencia entre 30 KB y 4 MB.
El poster es una imagen, y hay que tratarlo como tal
El atributo poster acepta la URL de una imagen que se pinta en la caja del vídeo hasta el primer fotograma. Y aquí viene el fallo sistemático: el póster es una imagen normal que no admite srcset, ni sizes, ni loading, ni fetchpriority. No hay negociación de resolución. Sirves un fichero y le vale a todo el mundo.
Las consecuencias son tres y todas cuestan.
Primera: el póster puede ser tu elemento del LCP. Un vídeo en la cabecera con un póster de 1920 píxeles pintado a ancho completo es un candidato perfecto a elemento mayor, y si ese póster es un PNG de 900 KB exportado del editor de vídeo, acabas de fijar tu LCP en varios segundos. Es un caso muy común porque el póster se genera con un ffmpeg rápido y nadie lo vuelve a mirar.
Segunda: no tienes control de prioridad. Como no admite fetchpriority, si el póster es el LCP y arranca en prioridad baja, no hay atributo que lo arregle. La solución es precargarlo explícitamente desde el <head>:
<link rel="preload" as="image" href="/media/portada-1200.avif" fetchpriority="high">
Tercera: no puedes servir una versión pequeña al móvil. La salida es servir un póster dimensionado para el caso mediano —1200 píxeles de ancho suele ser suficiente— en AVIF, que a esa resolución pesa entre 40 y 90 KB para una captura fotográfica. Si necesitas de verdad varias resoluciones, la alternativa es no usar el atributo y montar el póster como un <img> encima del vídeo, con toda la maquinaria de imágenes responsivas disponible, quitándolo al empezar la reproducción.
<div class="marco-video">
<img class="poster" src="portada-800.avif"
srcset="portada-600.avif 600w, portada-1000.avif 1000w, portada-1600.avif 1600w"
sizes="(min-width: 900px) 860px, 100vw"
width="1600" height="900" alt="" fetchpriority="high" decoding="async">
<video controls playsinline preload="none" width="1600" height="900">
<source src="clip-av1.mp4" type='video/mp4; codecs="av01.0.05M.08"'>
<source src="clip-h264.mp4" type='video/mp4; codecs="avc1.640028"'>
</video>
</div>
.marco-video { position: relative; aspect-ratio: 16 / 9; }
.marco-video > video,
.marco-video > .poster { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; object-fit: cover; }
.marco-video > .poster { z-index: 1; transition: opacity 200ms; }
.marco-video.reproduciendo > .poster { opacity: 0; pointer-events: none; }
const marco = document.querySelector('.marco-video');
marco.querySelector('video').addEventListener('playing', () => {
marco.classList.add('reproduciendo');
}, { once: true });
El alt vacío en el póster es correcto y deliberado: la imagen es decorativa porque el contenido informativo lo aporta el vídeo y sus subtítulos.
Este es el detalle que separa un vídeo que se siente pulido de uno que se siente roto, y no aparece en ninguna guía de rendimiento porque no es un problema de bytes.
Cuando el usuario pulsa reproducir, el navegador quita el póster y pinta el primer fotograma decodificado. Si el póster es una captura del segundo 3 y el vídeo arranca en el segundo 0, hay un salto visual brusco: la imagen cambia de golpe a otra escena antes de empezar a moverse. El usuario lo interpreta como un fallo. La cura es extraer el póster del fotograma exacto en el que arranca la reproducción, que normalmente es el cero:
ffmpeg -i clip.mp4 -vf "select=eq(n\,0)" -vframes 1 -q:v 2 poster.png
avifenc --min 20 --max 30 poster.png portada.avifY hay un segundo parpadeo, más sutil, que ocurre incluso con el fotograma correcto: el póster comprimido en AVIF con calidad media y el fotograma decodificado del vídeo no son idénticos a nivel de píxel, así que se ve un cambio de textura. Se disimula con una transición de opacidad de 150 a 250 milisegundos, como en el CSS de arriba, que es exactamente lo que hacen los reproductores buenos.
El tercer detalle, y este sí es de rendimiento puro: si el vídeo tiene autoplay, no le pongas póster pesado. El póster se descarga, se decodifica y se pinta, y acto seguido lo tapa el vídeo que ya venía cargando en paralelo. Son bytes y milisegundos de decodificación que compiten con el propio vídeo por el ancho de banda y por el hilo principal, para mostrarse durante 300 milisegundos. En reproducción automática, un color de fondo o un póster de 20 píxeles escalado hacen el mismo trabajo por dos órdenes de magnitud menos.
Medir lo que cuesta un vídeo que nadie ve
La comprobación es de un minuto y da datos que sorprenden. Abre la página con el panel de red, filtra por medios, y no toques el vídeo. Lo que veas transferido es el impuesto que pagan todos tus usuarios por un contenido que solo consume una fracción de ellos.
La cuenta que hay que hacer después es de negocio, no técnica: multiplica esos bytes por el número de visitas y divide por la tasa de reproducción real, que sacas de la analítica del reproductor. Si el vídeo lo reproduce el 8 por ciento de los visitantes y estás transfiriendo 400 KB de metadatos a todos, estás gastando doce veces más ancho de banda del que aprovechas.
Un caso que rompe la medición y hay que conocer: los vídeos incrustados de terceros mediante <iframe> no responden a nada de esto. El preload que controla el vídeo está dentro del documento del tercero y tú no lo tocas. Ahí la única herramienta es no cargar el iframe hasta que haga falta, sustituyéndolo por una fachada estática con el póster y un botón, que es la misma técnica que verás para cualquier incrustación pesada.
Toma una página tuya con un vídeo. Mide los bytes transferidos sin tocarlo, con preload en auto, en metadata y en none, en escritorio y con emulación de móvil. Anota las seis cifras. Después comprueba si el póster es el elemento del LCP mirando el marcador en el panel de rendimiento, y si lo es, mide cuánto baja el LCP al convertirlo a AVIF de 1200 píxeles y precargarlo desde el head.