Lazy Loading von Video- und Audioinhalten

Medienelemente (<video> und <audio>) und die zugehörigen Ressourcen (z. B. <video poster>-Bilder und vorab geladene Metadaten) verbrauchen beim ersten Seitenaufbau erhebliche Netzwerk- und Speicherbandbreite. Selbst wenn preload="metadata" oder preload="none" für ein <video> festgelegt ist, ruft der Browser das poster-Bild sofort ab und preload="metadata" initiiert weiterhin Bereichsanfragen für Medienheader für jeden Player auf der Seite.

Wenn Sie loading="lazy" zu nicht sichtbaren <video>- und <audio>-Elementen hinzufügen, wird der Browser angewiesen, alle Mediennetzwerkanfragen – einschließlich poster-Bilddownloads, preload-Hinweisen und autoplay-Ausführung – aufzuschieben, bis das Element einen vom Browser berechneten Abstand zum visuellen Darstellungsbereich erreicht.

Implementierung

  1. loading="lazy" zu nicht sichtbaren Medien hinzufügen: Wenden Sie loading="lazy" auf <video>- und <audio>-Elemente an, die sich „below the fold“ (mit Scrollen sichtbar) befinden. Lassen Sie das Attribut loading bei Medien im „above the fold“-Bereich weg, damit es standardmäßig auf eager gesetzt wird.
  2. Explizite Werte für width und height für <video>-Elemente festlegen: Geben Sie für <video> immer ganzzahlige Werte für die Attribute width und height (in CSS-Pixeln) an. Nicht geladene Videos haben standardmäßig die Abmessungen 300 × 150. Ohne explizite Größenangabe kann es passieren, dass ein Lazy-Loading-Video nicht in den sichtbaren Darstellungsbereich gelangt und nie geladen wird oder beim Laden störende Layoutverschiebungen (CLS) verursacht.
  3. Attribut controls in <audio>-Elementen einfügen: Fügen Sie controls immer in <audio loading="lazy"> ein. Da <audio>-Elemente ohne Browsersteuerelemente keine intrinsische visuelle Box haben, kann ein <audio loading="lazy">-Element ohne controls den Darstellungsbereich nicht sichtbar schneiden und wird nie geladen oder automatisch wiedergegeben.
  4. Koordinatenattribute preload und poster:
    • Bei <video loading="lazy"> werden sowohl der Abruf des poster-Bildes als auch das Verhalten von preload (none, metadata oder auto) verzögert, bis das Video in den Darstellungsbereich gelangt.
    • Kombinieren Sie loading="lazy" mit preload="none", wenn der Nutzer explizit auf „Wiedergabe“ drücken muss, nachdem er zu den Medien gescrollt hat, oder mit preload="metadata", wenn die Dauer und die Daten des ersten Frames automatisch ausgefüllt werden sollen, sobald der Player im Darstellungsbereich erscheint.
  5. Sicherer Umgang mit Lazy autoplay: Wenn loading="lazy" mit autoplay für ein stummgeschaltetes <video> (oder ein Opt-in-<audio controls>) kombiniert wird, werden Wiedergabe und Download erst gestartet, wenn sich das Element dem Darstellungsbereich nähert. Beachte immer @media (prefers-reduced-motion: reduce), bevor du Bewegtbildmedien automatisch abspielst.

Beispielcode

<!-- 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 Practices

  • OBLIGATORISCH: Legen Sie für <video>-Elemente immer explizite width- und height-Attribute fest. Nicht geladene Videos haben eine intrinsische Größe von 300 × 150 und werden nie geladen, wenn sie sich nicht mit einem sichtbaren Teil der Seite überschneiden.
  • OBLIGATORISCH: Geben Sie für <audio loading="lazy">-Elemente immer das Attribut controls an. Mit Lazy Loading geladene <audio>-Elemente ohne controls werden nicht sichtbar gerendert und nie geladen oder automatisch abgespielt.
  • Wende loading="lazy" NICHT auf „above the fold“- oder Largest Contentful Paint (LCP)-Elemente (<video>) an, da dies das Abrufen von poster-Bild- und Videodaten verzögert.
  • Lassen Sie das Attribut loading WEG, wenn das standardmäßige Eager Loading gewünscht ist, anstatt explizit loading="eager" zu schreiben.
  • Verlassen Sie sich NICHT auf das Ereignis window load, um zu erkennen, wann per Lazy Loading geladene <video>- oder <audio>-Elemente geladen wurden. Das Ereignis load wird nur für Medien ausgelöst, die mit Eager Loading geladen werden, auch wenn sich ein loading="lazy"-Medienelement anfangs im visuellen Darstellungsbereich befindet.
  • BEACHTEN SIE, dass Browser loading="lazy"-Medien nur dann zurückstellen, wenn JavaScript aktiviert ist. Dies ist eine Anti-Tracking-Maßnahme, die verhindert, dass Server die Scrollposition über Medienanfragen ableiten, wenn Scripting deaktiviert ist.

Fallback-Strategie

Wenn Ihr Baseline-Ziel loading="lazy" für <video> und <audio> nicht unterstützt, können Sie zwischen progressiver Verbesserung und einem IntersectionObserver-Fallback wählen, je nachdem, wie streng die Bandbreite eingespart werden muss:

Browser, die loading="lazy" für <video> und <audio> nicht erkennen, ignorieren das Attribut und greifen auf das Standardladen zurück, das dem Attribut preload unterliegt. Wenn Sie loading="lazy" mit preload="none" oder preload="metadata" kombinieren, wird verhindert, dass nicht unterstützte Browser die gesamte Mediendatei im Voraus herunterladen. <video poster>-Bilder und preload="metadata"-Header werden jedoch sofort abgerufen.

Fallback für benutzerdefinierten Code (IntersectionObserver)

Wenn Ihr Baseline-Ziel erfordert, dass <video poster>-Downloads, preload="metadata"-Anfragen oder die durch den Darstellungsbereich ausgelöste Wiedergabe in Browsern ohne native Unterstützung strikt verzögert werden, sollten Sie Object.hasOwn(HTMLMediaElement.prototype, 'loading') per Feature-Detection erkennen und dann auf IntersectionObserver zurückgreifen:

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