वीडियो और ऑडियो की लेज़ी लोडिंग

मीडिया एलिमेंट (<video> और <audio>) और उनसे जुड़े संसाधन (जैसे, <video poster> इमेज और पहले से लोड किया गया मेटाडेटा), पेज लोड के शुरुआती दौरान काफ़ी नेटवर्क और स्टोरेज बैंडविथ का इस्तेमाल करते हैं. <video> पर preload="metadata" या preload="none" सेट होने पर भी, ब्राउज़र तुरंत उसकी poster इमेज फ़ेच करता है. साथ ही, preload="metadata" अब भी पेज पर मौजूद हर प्लेयर के मीडिया हेडर के लिए, रेंज के अनुरोध शुरू करता है.

ऑफ़स्क्रीन <video> और <audio> एलिमेंट में loading="lazy" जोड़ने से, ब्राउज़र को सभी मीडिया नेटवर्क अनुरोधों को रोकने का निर्देश मिलता है. इनमें poster इमेज डाउनलोड, preload हिंट, और autoplay एक्ज़ीक्यूशन शामिल हैं. ऐसा तब तक होता है, जब तक एलिमेंट, विज़ुअल व्यूपोर्ट से ब्राउज़र के हिसाब से तय की गई दूरी तक नहीं पहुंच जाता.

इन्हें कैसे लागू करें

  1. ऑफ़स्क्रीन मीडिया में loading="lazy" जोड़ें: वेबसाइट में फ़ोल्ड के नीचे मौजूद <video> और <audio> एलिमेंट पर loading="lazy" लागू करें. पेज के ऊपरी हिस्से में मौजूद मीडिया पर loading एट्रिब्यूट का इस्तेमाल न करें, ताकि वह डिफ़ॉल्ट रूप से eager पर सेट हो जाए.
  2. <video> एलिमेंट पर width और height को साफ़ तौर पर सेट करें: <video> पर हमेशा पूर्णांक width और height एट्रिब्यूट (सीएसएस पिक्सल में) की जानकारी दें. अनलोड किए गए वीडियो, डिफ़ॉल्ट रूप से 300×150 डाइमेंशन पर सेट होते हैं. अगर वीडियो का साइज़ साफ़ तौर पर नहीं बताया गया है, तो हो सकता है कि लेज़ी-लोड किया गया वीडियो, दिखने वाले व्यूपोर्ट को इंटरसेक्ट न कर पाए और कभी लोड न हो. इसके अलावा, लोड होने पर लेआउट शिफ़्ट (सीएलएस) में रुकावट आ सकती है.
  3. <audio> एलिमेंट में controls एट्रिब्यूट शामिल करें: <audio loading="lazy"> में हमेशा controls शामिल करें. <audio> एलिमेंट में ब्राउज़र कंट्रोल के बिना कोई विज़ुअल बॉक्स नहीं होता. इसलिए, controls के बिना <audio loading="lazy"> एलिमेंट, व्यूपोर्ट को विज़ुअल तौर पर इंटरसेक्ट नहीं कर सकता. साथ ही, यह कभी लोड नहीं होगा या अपने-आप नहीं चलेगा.
  4. preload और poster एट्रिब्यूट को कोऑर्डिनेट करना:
    • <video loading="lazy"> पर, poster इमेज फ़ेच और preload व्यवहार (none, metadata या auto), दोनों को तब तक के लिए रोक दिया जाता है, जब तक वीडियो व्यूपोर्ट के पास नहीं पहुंच जाता.
    • loading="lazy" को preload="none" के साथ तब जोड़ें, जब उपयोगकर्ता को मीडिया पर स्क्रोल करने के बाद, 'चलाएं' बटन को साफ़ तौर पर दबाना पड़े. इसके अलावा, preload="metadata" को तब जोड़ें, जब प्लेयर के व्यूपोर्ट में आने के बाद, अवधि और शुरुआती फ़्रेम का डेटा अपने-आप भर जाए.
  5. लेज़ी autoplay को सुरक्षित तरीके से हैंडल करना: जब loading="lazy" को म्यूट किए गए <video> (या ऑप्ट-इन <audio controls>) पर autoplay के साथ जोड़ा जाता है, तो वीडियो तब तक नहीं चलता और डाउनलोड नहीं होता, जब तक कि एलिमेंट व्यूपोर्ट के पास नहीं पहुंच जाता. मोशन मीडिया को अपने-आप चलाने से पहले, हमेशा @media (prefers-reduced-motion: reduce) का पालन करें.

कोड का उदाहरण

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

सबसे सही तरीके

  • ज़रूरी: <video> एलिमेंट पर, width और height एट्रिब्यूट की वैल्यू हमेशा साफ़ तौर पर सेट करें. लोड न होने वाले वीडियो का साइज़ 300×150 होता है. अगर वे पेज के दिखने वाले हिस्से को इंटरसेक्ट नहीं करते हैं, तो वे कभी लोड नहीं होंगे.
  • ज़रूरी: <audio loading="lazy"> एलिमेंट में हमेशा controls एट्रिब्यूट शामिल करें. controls के बिना लेज़ी-लोड किए गए <audio> एलिमेंट, साफ़ तौर पर रेंडर नहीं होते. साथ ही, वे कभी लोड नहीं होंगे या अपने-आप नहीं चलेंगे.
  • पेज के सबसे ऊपर दिखने वाले हिस्से या सबसे बड़े एलिमेंट को रेंडर करने में लगने वाले समय (एलसीपी) <video> से जुड़े एलिमेंट पर loading="lazy" लागू न करें. ऐसा करने से, poster इमेज और वीडियो, दोनों का डेटा फ़ेच होने में देरी होती है.
  • ज़रूर करें: अगर आपको loading="eager" को साफ़ तौर पर लिखने के बजाय, डिफ़ॉल्ट ईगर लोडिंग का इस्तेमाल करना है, तो loading एट्रिब्यूट को हटा दें.
  • <video> या <audio> एलिमेंट के लोड होने का पता लगाने के लिए, window load इवेंट पर भरोसा न करें. load इवेंट सिर्फ़ ईगर-लोड किए गए मीडिया के आधार पर ट्रिगर होता है. भले ही, loading="lazy" मीडिया एलिमेंट शुरुआत में विज़ुअल व्यूपोर्ट में मौजूद हो.
  • ध्यान दें कि ब्राउज़र, loading="lazy" मीडिया को सिर्फ़ तब रोकते हैं, जब JavaScript चालू हो. यह ट्रैकिंग रोकने का एक तरीका है. इससे सर्वर, स्क्रिप्टिंग बंद होने पर मीडिया अनुरोधों के ज़रिए स्क्रोल की पोज़िशन का अनुमान नहीं लगा पाते.

फ़ॉलबैक रणनीति

अगर आपका बेसलाइन टारगेट, <video> और <audio> पर loading="lazy" के साथ काम नहीं करता है, तो प्रोग्रेसिव एन्हांसमेंट और IntersectionObserver फ़ॉलबैक में से कोई एक चुनें. यह इस बात पर निर्भर करता है कि बैंडविड्थ को कितनी सख्ती से सुरक्षित रखना है:

<video> और <audio> पर loading="lazy" को न पहचानने वाले ब्राउज़र, एट्रिब्यूट को अनदेखा कर देते हैं. साथ ही, preload एट्रिब्यूट से नियंत्रित स्टैंडर्ड लोडिंग पर वापस चले जाते हैं. loading="lazy" को preload="none" या preload="metadata" के साथ जोड़ने से यह पक्का होता है कि काम न करने वाले ब्राउज़र, पूरी मीडिया फ़ाइल को पहले से डाउनलोड न करें. हालांकि, <video poster> इमेज और preload="metadata" हेडर को तुरंत फ़ेच किया जाएगा.

कस्टम कोड फ़ॉलबैक (IntersectionObserver)

जब आपके बेसलाइन टारगेट के लिए, <video poster> डाउनलोड, preload="metadata" अनुरोधों या नेटिव सपोर्ट के बिना ब्राउज़र में व्यूपोर्ट से ट्रिगर होने वाले प्लेबैक को पूरी तरह से टालना ज़रूरी हो, तो सुविधा का पता लगाएं Object.hasOwn(HTMLMediaElement.prototype, 'loading'), फिर 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);
  }
}