Leniwe ładowanie treści wideo i audio

Elementy multimedialne (<video> i <audio>) oraz powiązane z nimi zasoby (takie jak obrazy <video poster> i wstępnie załadowane metadane) zużywają znaczną przepustowość sieci i pamięci podczas początkowego wczytywania strony. Nawet jeśli atrybut preload="metadata" lub preload="none" jest ustawiony w elemencie <video>, przeglądarka natychmiast pobiera obraz poster, a atrybut preload="metadata" nadal inicjuje żądania zakresu dotyczące nagłówków multimediów we wszystkich odtwarzaczach na stronie.

Dodanie atrybutu loading="lazy" do elementów <video> i <audio> poza ekranem powoduje, że przeglądarka odracza wszystkie żądania sieciowe dotyczące multimediów – w tym pobieranie obrazów poster, wskazówki preload i wykonywanie autoplay – dopóki element nie osiągnie obliczonej przez przeglądarkę odległości od widocznego obszaru.

Jak to zrobić?

  1. Dodaj loading="lazy" do multimediów poza ekranem: zastosuj loading="lazy" do elementów <video> i <audio> umieszczonych w części strony widocznej po przewinięciu. Pomiń atrybut loading w przypadku multimediów widocznych na ekranie, aby domyślnie ustawić wartość eager.
  2. Ustaw jawne wartości width i height w elementach <video>: zawsze określaj atrybuty width i height (w pikselach CSS) jako liczby całkowite w elementach <video>. Niewczytane filmy mają domyślnie wymiary 300 × 150. Bez wyraźnego określenia rozmiaru film ładowany leniwie może nie przecinać widocznego obszaru i nigdy się nie wczytać lub powodować uciążliwe przesunięcia układu (CLS) podczas wczytywania.
  3. Uwzględnij atrybut controls w elementach <audio>: zawsze uwzględniaj atrybut controls w elementach <audio loading="lazy">. Elementy <audio> nie mają wewnętrznego pola wizualnego bez elementów sterujących przeglądarki, więc element <audio loading="lazy"> bez controls nie może w widoczny sposób przecinać widocznego obszaru i nigdy nie zostanie wczytany ani automatycznie odtworzony.
  4. Atrybuty współrzędnych preload i poster:
    • W przypadku <video loading="lazy"> zarówno pobieranie obrazu poster, jak i działanie preload (none, metadata lub auto) są odroczone do momentu, gdy film zbliży się do widocznego obszaru.
    • Połącz loading="lazy" z preload="none", gdy użytkownik musi wyraźnie nacisnąć przycisk odtwarzania po przewinięciu do multimediów, lub z preload="metadata", gdy dane o czasie trwania i początkowej klatce powinny być wypełniane automatycznie po wejściu odtwarzacza do widocznego obszaru.
  5. Bezpieczne korzystanie z atrybutu autoplay: gdy atrybut loading="lazy" jest połączony z atrybutem autoplay w wyciszonym elemencie <video> (lub elemencie <audio controls> z opcją akceptacji), odtwarzanie i pobieranie są wstrzymywane, dopóki element nie zbliży się do widocznego obszaru. Zawsze przestrzegaj ustawienia @media (prefers-reduced-motion: reduce) przed automatycznym odtwarzaniem multimediów ruchomych.

Przykładowy kod

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

Sprawdzone metody

  • OBOWIĄZKOWE: zawsze ustawiaj jawne atrybuty width i height w elementach <video>. Niewczytane filmy mają rozmiar wewnętrzny 300 × 150 i nigdy się nie wczytają, jeśli nie przecinają widocznej części strony.
  • OBOWIĄZKOWO: zawsze dodawaj atrybut controls do elementów <audio loading="lazy">. Elementy <audio> ładowane leniwie bez atrybutu controls nie są renderowane w widoczny sposób i nigdy nie zostaną załadowane ani automatycznie odtworzone.
  • NIE stosuj atrybutu loading="lazy" do elementów widocznych w górnej części strony ani do elementów największego wyrenderowania treści (LCP) <video>, ponieważ opóźnia to pobieranie danych zarówno obrazu, jak i filmu poster.
  • POMIŃ atrybut loading, jeśli chcesz użyć domyślnego ładowania natychmiastowego, zamiast jawnie wpisywać loading="eager".
  • NIE polegaj na zdarzeniu window load, aby wykrywać, kiedy elementy <video> lub <audio> zostały wczytane metodą leniwego ładowania. Zdarzenie load jest wywoływane tylko na podstawie multimediów wczytywanych od razu, nawet jeśli element multimedialny loading="lazy" znajduje się początkowo w widocznym obszarze.
  • PAMIĘTAJ, że przeglądarki odraczają ładowanie multimediów loading="lazy" tylko wtedy, gdy JavaScript jest włączony (jest to środek zapobiegający śledzeniu, który uniemożliwia serwerom wnioskowanie o pozycji przewijania na podstawie żądań multimediów, gdy skrypty są wyłączone).

Strategia zastępcza

Jeśli Twój cel Baseline nie obsługuje loading="lazy" na <video> i <audio>, wybierz między progresywnym ulepszaniem a IntersectionObserver kreacją zastępczą, biorąc pod uwagę, jak rygorystycznie należy oszczędzać przepustowość:

Przeglądarki, które nie rozpoznają atrybutu loading="lazy" w tagach <video> i <audio>, ignorują go i wracają do standardowego wczytywania, którym zarządza atrybut preload. Połączenie loading="lazy" z preload="none" lub preload="metadata" sprawia, że nieobsługiwane przeglądarki nie pobierają od razu całego pliku multimedialnego, chociaż obrazy <video poster> i nagłówki preload="metadata" będą pobierane z wyprzedzeniem.

Zastępczy kod niestandardowy (IntersectionObserver)

Jeśli Twój cel Baseline wymaga ścisłego odroczenia pobierania <video poster>, żądań preload="metadata" lub odtwarzania wywoływanego przez widoczny obszar w przeglądarkach bez natywnej obsługi, wykryj funkcję Object.hasOwn(HTMLMediaElement.prototype, 'loading'), a następnie użyj 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);
  }
}