Video e audio con caricamento lento

Gli elementi multimediali (<video> e <audio>) e le risorse associate (come le immagini <video poster> e i metadati precaricati) consumano una larghezza di banda di rete e di archiviazione significativa durante il caricamento iniziale della pagina. Anche quando preload="metadata" o preload="none" è impostato su un <video>, il browser recupera immediatamente l'immagine poster e preload="metadata" avvia comunque le richieste di intervallo per le intestazioni multimediali in ogni player della pagina.

L'aggiunta di loading="lazy" agli elementi <video> e <audio> fuori schermo indica al browser di posticipare tutte le richieste di rete multimediali, inclusi i download di immagini poster, i suggerimenti preload e l'esecuzione di autoplay, finché l'elemento non raggiunge una distanza calcolata dal browser dalla visual viewport.

Come implementare

  1. Aggiungi loading="lazy" ai contenuti multimediali fuori schermo: applica loading="lazy" agli elementi <video> e <audio> posizionati below the fold. Ometti l'attributo loading sui contenuti multimediali above the fold in modo che il valore predefinito sia eager.
  2. Imposta width e height espliciti sugli elementi <video>: specifica sempre gli attributi width e height interi (in pixel CSS) su <video>. I video non caricati hanno dimensioni predefinite di 300×150; senza un dimensionamento esplicito, un video con caricamento lento potrebbe non intersecare l'area visibile e non caricarsi mai o causare variazioni del layout cumulative (CLS) fastidiose durante il caricamento.
  3. Includi l'attributo controls negli elementi <audio>: includi sempre controls in <audio loading="lazy">. Poiché gli elementi <audio> non hanno una casella visiva intrinseca senza i controlli del browser, un elemento <audio loading="lazy"> senza controls non può intersecare visibilmente l'area visibile e non verrà mai caricato o riprodotto automaticamente.
  4. Attributi delle coordinate preload e poster:
    • Il giorno <video loading="lazy">, sia il recupero dell'immagine poster sia il comportamento preload (none, metadata o auto) verranno posticipati fino a quando il video non si avvicina all'area visibile.
    • Associa loading="lazy" a preload="none" quando l'utente deve premere esplicitamente il pulsante di riproduzione dopo aver fatto scorrere i contenuti multimediali o a preload="metadata" quando i dati sulla durata e sul frame iniziale devono essere compilati automaticamente una volta che il player entra nell'area visibile.
  5. Gestisci il caricamento lento di autoplay in modo sicuro: quando loading="lazy" viene combinato con autoplay su un <video> disattivato (o un <audio controls> con attivazione), la riproduzione e il download attendono che l'elemento si avvicini alla viewport. Rispetta sempre @media (prefers-reduced-motion: reduce) prima di riprodurre automaticamente i contenuti multimediali in movimento.

Esempio di codice

<!-- 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);
});

Best practice

  • OBBLIGATORIO: imposta sempre attributi width e height espliciti sugli elementi <video>. I video non caricati hanno una dimensione intrinseca di 300 × 150 e non verranno mai caricati se non intersecano una parte visibile della pagina.
  • OBBLIGATORIO: includi sempre l'attributo controls negli elementi <audio loading="lazy">. Gli elementi <audio> con caricamento lento senza controls non vengono visualizzati e non verranno mai caricati o riprodotti automaticamente.
  • NON applicare loading="lazy" il recupero ritardato <video> agli elementi above the fold o Largest Contentful Paint (LCP), in quanto ritarda il recupero dei dati di immagini e video poster.
  • DO: ometti l'attributo loading quando vuoi utilizzare il caricamento eager predefinito, anziché scrivere esplicitamente loading="eager".
  • NON fare affidamento sull'evento window load per rilevare quando sono stati caricati gli elementi <video> o <audio> con caricamento lento. L'evento load viene attivato solo in base ai contenuti multimediali con caricamento eager, anche se un elemento multimediale loading="lazy" si trova inizialmente all'interno dell'area visibile.
  • RICORDA che i browser rinviano i contenuti multimediali loading="lazy" solo quando JavaScript è abilitato (una misura anti-tracciamento per impedire ai server di dedurre la posizione di scorrimento tramite richieste di contenuti multimediali quando lo scripting è disabilitato).

Strategia di riserva

Se il tuo target Baseline non supporta loading="lazy" su <video> e <audio>, scegli tra il potenziamento progressivo e un IntersectionObserver di riserva in base alla rigidità con cui deve essere conservata la larghezza di banda:

I browser che non riconoscono loading="lazy" su <video> e <audio> ignorano l'attributo e tornano al caricamento standard regolato dall'attributo preload. L'accoppiamento di loading="lazy" con preload="none" o preload="metadata" garantisce che i browser non supportati evitino comunque di scaricare l'intero file multimediale in anticipo, anche se le immagini <video poster> e le intestazioni preload="metadata" verranno recuperate in modo eager.

Codice di riserva personalizzato (IntersectionObserver)

Quando il tuo target Baseline richiede di posticipare rigorosamente i download di <video poster>, le richieste di preload="metadata" o la riproduzione attivata dalla viewport nei browser senza supporto nativo, esegui il rilevamento delle funzionalità di Object.hasOwn(HTMLMediaElement.prototype, 'loading'), quindi esegui il fallback 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);
  }
}