Pemuatan lambat video dan audio

Elemen media (<video> dan <audio>) serta resource terkaitnya (seperti gambar <video poster> dan metadata yang dipramuat) menggunakan bandwidth jaringan dan penyimpanan yang signifikan selama pemuatan halaman awal. Meskipun preload="metadata" atau preload="none" ditetapkan pada <video>, browser akan segera mengambil gambar poster, dan preload="metadata" masih memulai permintaan rentang untuk header media di setiap pemutar di halaman.

Menambahkan loading="lazy" ke elemen <video> dan <audio> di luar layar akan menginstruksikan browser untuk menunda semua permintaan jaringan media—termasuk download gambar poster, petunjuk preload, dan eksekusi autoplay—hingga elemen mencapai jarak yang dihitung browser dari area pandang visual.

Cara menerapkan

  1. Tambahkan loading="lazy" ke media di luar layar: Terapkan loading="lazy" ke elemen <video> dan <audio> yang diposisikan di paruh bawah. Hilangkan atribut loading pada media paruh atas sehingga ditetapkan secara default ke eager.
  2. Tetapkan width dan height eksplisit pada elemen <video>: Selalu tentukan atribut width dan height bilangan bulat (dalam piksel CSS) pada <video>. Video yang tidak dimuat akan ditetapkan ke dimensi 300×150 secara default; tanpa ukuran eksplisit, video yang dimuat lambat mungkin gagal berpotongan dengan area pandang yang terlihat dan tidak pernah dimuat, atau menyebabkan pergeseran tata letak (CLS) yang mengganggu saat dimuat.
  3. Sertakan atribut controls pada elemen <audio>: Selalu sertakan controls pada <audio loading="lazy">. Karena elemen <audio> tidak memiliki kotak visual intrinsik tanpa kontrol browser, elemen <audio loading="lazy"> tanpa controls tidak dapat terlihat berpotongan dengan area tampilan dan tidak akan pernah dimuat atau diputar otomatis.
  4. Atribut koordinat preload dan poster:
    • Di <video loading="lazy">, pengambilan gambar poster dan perilaku preload (none, metadata, atau auto) ditangguhkan hingga video mendekati area pandang.
    • Pasangkan loading="lazy" dengan preload="none" saat pengguna harus secara eksplisit menekan putar setelah men-scroll ke media, atau preload="metadata" saat durasi dan data frame awal harus diisi secara otomatis setelah pemutar memasuki area pandang.
  5. Menangani autoplay lambat dengan aman: Jika loading="lazy" digabungkan dengan autoplay pada <video> yang dibisukan (atau <audio controls> yang diaktifkan), pemutaran dan download akan menunggu hingga elemen mendekati area pandang. Selalu hormati @media (prefers-reduced-motion: reduce) sebelum memutar otomatis media bergerak.

Kode contoh

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

Praktik terbaik

  • WAJIB: Selalu tetapkan atribut width dan height eksplisit pada elemen <video>. Video yang tidak dimuat memiliki ukuran intrinsik 300×150 dan tidak akan pernah dimuat jika tidak berpotongan dengan bagian halaman yang terlihat.
  • WAJIB: Selalu sertakan atribut controls pada elemen <audio loading="lazy">. Elemen <audio> yang dimuat lambat tanpa controls tidak dirender secara terlihat dan tidak akan pernah dimuat atau diputar otomatis.
  • JANGAN terapkan loading="lazy" ke elemen paruh atas atau Largest Contentful Paint (LCP) <video>, karena akan menunda pengambilan data gambar dan video poster.
  • LAKUKAN penghilangan atribut loading jika pemuatan eager default diinginkan, daripada menulis loading="eager" secara eksplisit.
  • JANGAN mengandalkan peristiwa window load untuk mendeteksi kapan elemen <video> atau <audio> yang dimuat lambat telah dimuat. Peristiwa load diaktifkan hanya berdasarkan media yang dimuat secara eager, meskipun elemen media loading="lazy" awalnya berada di dalam area pandang visual.
  • PERHATIKAN bahwa browser hanya menunda media loading="lazy" jika JavaScript diaktifkan (tindakan antipelacakan untuk mencegah server menyimpulkan posisi scroll melalui permintaan media saat pembuatan skrip dinonaktifkan).

Strategi penggantian

Jika target Dasar Pengukuran Anda tidak mendukung loading="lazy" di <video> dan <audio>, pilih antara progressive enhancement dan penggantian IntersectionObserver berdasarkan seberapa ketat bandwidth harus dihemat:

Browser yang tidak mengenali loading="lazy" di <video> dan <audio> akan mengabaikan atribut tersebut dan kembali ke pemuatan standar yang diatur oleh atribut preload. Menyandingkan loading="lazy" dengan preload="none" atau preload="metadata" memastikan browser yang tidak didukung tetap menghindari download file media lengkap di awal, meskipun gambar <video poster> dan header preload="metadata" akan diambil secara eager.

Penggantian kode kustom (IntersectionObserver)

Jika target Dasar Pengukuran Anda memerlukan penundaan ketat untuk download <video poster>, permintaan preload="metadata", atau pemutaran yang dipicu area pandang di browser tanpa dukungan native, deteksi fitur Object.hasOwn(HTMLMediaElement.prototype, 'loading'), lalu gunakan IntersectionObserver sebagai pengganti:

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