Video ve ses dosyalarını geç yükleme

Medya öğeleri (<video> ve <audio>) ve bunlarla ilişkili kaynaklar (ör. <video poster> resimler ve önceden yüklenmiş meta veriler), ilk sayfa yüklemesi sırasında önemli miktarda ağ ve depolama bant genişliği tüketir. <video> üzerinde preload="metadata" veya preload="none" ayarlanmış olsa bile tarayıcı, poster resmini hemen getirir ve preload="metadata", sayfadaki her oynatıcıda medya başlıkları için aralık istekleri başlatmaya devam eder.

Ekran dışı <video> ve <audio> öğelerine loading="lazy" eklemek, tarayıcıya poster resim indirmeleri, preload ipuçları ve autoplay yürütme dahil olmak üzere tüm medya ağı isteklerini, öğe görsel görüntü alanından tarayıcı tarafından hesaplanan bir mesafeye ulaşana kadar ertelemesini söyler.

Nasıl uygulanır?

  1. Ekran dışı medyaya loading="lazy" ekleyin: Ekranın alt kısmında yer alan <video> ve <audio> öğelerine loading="lazy" uygulayın. Ekranın üst kısmındaki medyada loading özelliğini atlayarak varsayılan olarak eager değerini almasını sağlayın.
  2. <video> öğelerinde açıkça width ve height değerleri ayarlayın: <video> öğelerinde her zaman tam sayı width ve height özelliklerini (CSS pikseli cinsinden) belirtin. Yüklenmeyen videolar varsayılan olarak 300×150 boyutlarındadır. Açıkça boyutlandırma yapılmazsa geç yüklenen bir video, görünür görüntü alanıyla kesişmeyebilir ve hiç yüklenmeyebilir ya da yüklendiğinde rahatsız edici düzen kaymalarına (CLS) neden olabilir.
  3. <audio> öğelerine controls özelliğini ekleyin: <audio loading="lazy"> öğelerine her zaman controls özelliğini ekleyin. <audio> öğelerinin tarayıcı kontrolleri olmadan içsel görsel kutusu olmadığından, controls içermeyen bir <audio loading="lazy"> öğesi görüntü alanıyla görünür şekilde kesişemez ve hiçbir zaman yüklenmez veya otomatik olarak oynatılmaz.
  4. Koordinat preload ve poster özellikleri:
    • <video loading="lazy"> üzerinde, hem poster resim getirme hem de preload davranışı (none, metadata veya auto), video görüntüleme alanına yaklaşana kadar ertelenir.
    • Kullanıcının medyaya kaydırdıktan sonra oynat düğmesine açıkça basması gerektiğinde loading="lazy" ile preload="none", oynatıcı görüntü alanına girdiğinde süre ve ilk kare verilerinin otomatik olarak doldurulması gerektiğinde ise preload="metadata" eşleyin.
  5. Yavaş yüklemeyi autoplay güvenli bir şekilde işleme: loading="lazy", sesi kapatılmış bir <video> (veya etkinleştirilmiş bir <audio controls>) üzerinde autoplay ile birleştirildiğinde, oynatma ve indirme işlemleri öğe görüntü alanına yaklaşana kadar bekler. Hareketli medyayı otomatik olarak oynatmadan önce her zaman @media (prefers-reduced-motion: reduce) ayarına uyun.

Örnek 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);
});

En iyi uygulamalar

  • ZORUNLU: <video> öğelerinde her zaman açık width ve height özelliklerini ayarlayın. Yüklenmemiş videoların iç boyutu 300×150 olur ve sayfanın görünür bir bölümüyle kesişmezlerse hiçbir zaman yüklenmezler.
  • ZORUNLU: <audio loading="lazy"> öğelerine her zaman controls özelliğini ekleyin. controls içermeyen geç yüklenen <audio> öğeleri görünür şekilde oluşturulmaz ve hiçbir zaman yüklenmez veya otomatik olarak oynatılmaz.
  • poster resim ve video verilerinin getirilmesini geciktirdiğinden, ekranın üst kısmındaki veya Largest Contentful Paint (LCP) <video> öğelerine loading="lazy" UYGULAMAYIN.
  • loading="eager" değerini açıkça yazmak yerine varsayılan istekli yükleme tercih edildiğinde loading özelliğini atlayın.
  • Geç yüklenen <video> veya <audio> öğelerinin ne zaman yüklendiğini algılamak için window load etkinliğine GÜVENMEYİN. load etkinliği, loading="lazy" medya öğesi başlangıçta görsel görüntü alanının içinde olsa bile yalnızca istekli yüklenen medyaya göre tetiklenir.
  • UNUTMAYIN: Tarayıcılar, loading="lazy" medyasını yalnızca JavaScript etkinleştirildiğinde erteler (komut dosyası oluşturma devre dışı bırakıldığında sunucuların medya istekleri aracılığıyla kaydırma konumunu tahmin etmesini önlemek için izleme önleme önlemi).

Yedek strateji

Baseline hedefiniz <video> ve <audio> üzerinde loading="lazy" desteklemiyorsa bant genişliğinin ne kadar sıkı bir şekilde korunması gerektiğine bağlı olarak progresif geliştirme ile IntersectionObserver yedek arasında seçim yapın:

<video> ve <audio> öğelerinde loading="lazy" özelliğini tanımayan tarayıcılar bu özelliği yoksayar ve preload özelliği tarafından yönetilen standart yüklemeye geri döner. loading="lazy" ile preload="none" veya preload="metadata" eşleştirildiğinde, desteklenmeyen tarayıcıların medya dosyasının tamamını önceden indirmesi engellenir. Ancak <video poster> resimler ve preload="metadata" başlıklar istekli olarak getirilir.

Özel kod yedeklemesi (IntersectionObserver)

Baseline hedefiniz, yerel destek sunmayan tarayıcılarda <video poster> indirmelerini, preload="metadata" isteklerini veya görüntü alanıyla tetiklenen oynatmayı kesin olarak ertelemeyi gerektirdiğinde Object.hasOwn(HTMLMediaElement.prototype, 'loading') özelliğini algılayın ve ardından IntersectionObserver özelliğine geri dönün:

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