Los elementos multimedia (<video> y <audio>) y sus recursos asociados (como las imágenes <video poster> y los metadatos precargados) consumen un ancho de banda de red y almacenamiento significativo durante la carga de página inicial. Incluso cuando se establece preload="metadata" o preload="none" en un <video>, el navegador recupera con entusiasmo su imagen poster de inmediato, y preload="metadata" sigue iniciando solicitudes de rango para los encabezados de contenido multimedia en todos los reproductores de la página.
Agregar loading="lazy" a los elementos <video> y <audio> fuera de la pantalla le indica al navegador que aplace todas las solicitudes de red de contenido multimedia, incluidas las descargas de imágenes poster, las sugerencias de preload y la ejecución de autoplay, hasta que el elemento alcance una distancia calculada por el navegador desde el viewport visual.
Cómo implementarlo
- Agrega
loading="lazy"al contenido multimedia fuera de pantalla: Aplicaloading="lazy"a los elementos<video>y<audio>ubicados en la mitad inferior de la página. Omite el atributoloadingen el contenido multimedia que aparece en la mitad superior de la página para que se establezca de forma predeterminada eneager. - Establece valores explícitos de
widthyheighten los elementos<video>: Siempre especifica los atributos enteros dewidthyheight(en píxeles de CSS) en<video>. Los videos que no se cargan tienen dimensiones predeterminadas de300×150. Sin un tamaño explícito, es posible que un video de carga diferida no se cruce con el viewport visible y nunca se cargue, o bien que cause cambios de diseño disruptivos (CLS) cuando se cargue. - Incluye el atributo
controlsen los elementos<audio>: Incluye siemprecontrolsen<audio loading="lazy">. Debido a que los elementos<audio>no tienen un cuadro visual intrínseco sin controles del navegador, un elemento<audio loading="lazy">sincontrolsno puede cruzarse de forma visible con el viewport y nunca se cargará ni se reproducirá automáticamente. - Atributos
preloadyposterde Coordinate:- En
<video loading="lazy">, tanto la recuperación de imágenes depostercomo el comportamiento depreload(none,metadataoauto) se aplazan hasta que el video se acerca al viewport. - Vincula
loading="lazy"conpreload="none"cuando el usuario deba presionar Reproducir de forma explícita después de desplazarse hasta el contenido multimedia, opreload="metadata"cuando los datos de duración y fotograma inicial deban completarse automáticamente una vez que el reproductor ingrese al viewport.
- En
- Controla
autoplaydiferido de forma segura: Cuandoloading="lazy"se combina conautoplayen un<video>silenciado (o un<audio controls>de habilitación), la reproducción y la descarga esperan hasta que el elemento se acerque al viewport. Siempre respeta@media (prefers-reduced-motion: reduce)antes de reproducir automáticamente contenido multimedia en movimiento.
Ejemplo de código
<!-- Above-the-fold hero video: DO NOT use loading="lazy".
Omit loading (defaults to "eager") so the poster and initial media load immediately. -->
<video
controls
width="1280"
height="720"
poster="/images/hero-poster.jpg"
preload="metadata"
>
<source src="/media/keynote.webm" type="video/webm">
<source src="/media/keynote.mp4" type="video/mp4">
<track src="/media/keynote-en.vtt" kind="captions" srclang="en" label="English">
</video>
<!-- Below-the-fold video player:
MANDATORY: Specify integer width and height attributes so the unloaded video
has a non-zero layout box to intersect the viewport and avoids layout shift.
Both the poster image and preload="metadata" are deferred until near the viewport. -->
<video
controls
loading="lazy"
width="800"
height="450"
poster="/images/tutorial-poster.jpg"
preload="metadata"
>
<source src="/media/tutorial.webm" type="video/webm">
<source src="/media/tutorial.mp4" type="video/mp4">
<track src="/media/tutorial-en.vtt" kind="captions" srclang="en" label="English">
</video>
<!-- Below-the-fold inline product loop:
With loading="lazy" and autoplay, downloading and playback wait until the
video approaches the viewport. Include muted and playsinline for autoplay. -->
<video
controls
loading="lazy"
autoplay
loop
muted
playsinline
width="640"
height="360"
poster="/images/feature-loop-poster.jpg"
>
<source src="/media/feature-loop.webm" type="video/webm">
<source src="/media/feature-loop.mp4" type="video/mp4">
<track src="/media/feature-loop-en.vtt" kind="captions" srclang="en" label="English">
</video>
<!-- Below-the-fold audio player:
MANDATORY: Include the controls attribute. Lazy-loaded <audio> without
controls is invisible, cannot intersect the viewport, and will never load. -->
<figure>
<figcaption id="episode-title">Episode 42: Browser Architecture Deep Dive</figcaption>
<audio
controls
loading="lazy"
preload="metadata"
src="/media/episode-42.mp3"
aria-labelledby="episode-title"
aria-details="episode-transcript"
></audio>
<details id="episode-transcript">
<summary>View transcript</summary>
<p>Transcript content for Episode 42...</p>
</details>
</figure>
// Pause autoplaying videos when the user prefers reduced motion
const reducedMotionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
function syncReducedMotion(prefersReducedMotion) {
for (const video of document.querySelectorAll('video[autoplay]')) {
if (prefersReducedMotion) {
video.removeAttribute('autoplay');
video.pause();
}
}
}
syncReducedMotion(reducedMotionQuery.matches);
reducedMotionQuery.addEventListener('change', (event) => {
syncReducedMotion(event.matches);
});
Prácticas recomendadas
- OBLIGATORIO: Siempre establece atributos
widthyheightexplícitos en los elementos<video>. Los videos que no se cargan tienen un tamaño intrínseco de300×150y nunca se cargarán si no se cruzan con una parte visible de la página. - OBLIGATORIO: Incluye siempre el atributo
controlsen los elementos<audio loading="lazy">. Los elementos<audio>de carga diferida sincontrolsno se renderizan de forma visible y nunca se cargarán ni se reproducirán automáticamente. - NO apliques
loading="lazy"a los elementos<video>de la mitad superior de la página o de Largest Contentful Paint (LCP), ya que retrasa la recuperación de los datos de imagen y video deposter. - SÍ omitas el atributo
loadingcuando se desee la carga inmediata predeterminada en lugar de escribir explícitamenteloading="eager". - NO confíes en el evento
loaddewindowpara detectar cuándo se cargaron los elementos<video>o<audio>de carga diferida. El eventoloadse activa solo en función del contenido multimedia de carga inmediata, incluso si un elemento multimedialoading="lazy"se encuentra inicialmente dentro del viewport visual. - SÍ ten en cuenta que los navegadores solo aplazan el contenido multimedia
loading="lazy"cuando JavaScript está habilitado (una medida antirastreo para evitar que los servidores infieran la posición de desplazamiento a través de solicitudes de contenido multimedia cuando las secuencias de comandos están inhabilitadas).
Estrategia de resguardo
Si tu objetivo de referencia no admite loading="lazy" en <video> y <audio>, elige entre la mejora progresiva y un resguardo de IntersectionObserver según el nivel de rigurosidad con el que se deba conservar el ancho de banda:
Mejora progresiva (predeterminada recomendada)
Los navegadores que no reconocen loading="lazy" en <video> y <audio> ignoran el atributo y recurren a la carga estándar regida por el atributo preload. La vinculación de loading="lazy" con preload="none" o preload="metadata" garantiza que los navegadores no compatibles eviten descargar el archivo multimedia completo por adelantado, aunque las imágenes <video poster> y los encabezados preload="metadata" se recuperarán con entusiasmo.
Resguardo de código personalizado (IntersectionObserver)
Cuando tu objetivo de Baseline requiera diferir estrictamente las descargas de <video poster>, las solicitudes de preload="metadata" o la reproducción activada por el viewport en navegadores sin compatibilidad nativa, detecta la función Object.hasOwn(HTMLMediaElement.prototype, 'loading') y, luego, recurre a IntersectionObserver:
// Feature-detect native media lazy loading on HTMLMediaElement.prototype
const supportsLazyMedia = Object.hasOwn(HTMLMediaElement.prototype, 'loading');
if (!supportsLazyMedia) {
const lazyMediaElements = document.querySelectorAll(
'video[loading="lazy"], audio[loading="lazy"][controls]',
);
// Example-only rootMargin value; adjust distance threshold for your layout
const mediaObserver = new IntersectionObserver(
(entries, observer) => {
for (const entry of entries) {
if (!entry.isIntersecting) continue;
const media = entry.target;
// Restore deferred poster and preload attributes when entering the viewport
if (media.dataset.poster) {
media.poster = media.dataset.poster;
delete media.dataset.poster;
}
if (media.dataset.preload) {
media.preload = media.dataset.preload;
delete media.dataset.preload;
}
if (media.dataset.src) {
media.src = media.dataset.src;
delete media.dataset.src;
}
for (const source of media.querySelectorAll('source[data-src]')) {
source.src = source.dataset.src;
delete source.dataset.src;
}
media.load();
observer.unobserve(media);
}
},
{ rootMargin: '200px 0px' },
);
for (const media of lazyMediaElements) {
// Hold back network fetches in unsupported browsers until intersection
if (media.hasAttribute('poster')) {
media.dataset.poster = media.getAttribute('poster');
media.removeAttribute('poster');
}
if (media.hasAttribute('preload') && media.getAttribute('preload') !== 'none') {
media.dataset.preload = media.getAttribute('preload');
}
media.preload = 'none';
mediaObserver.observe(media);
}
}