بارگیری کُند ویدیو و صوت

عناصر رسانه (<video> و <audio>) و منابع مرتبط با آن‌ها (مانند تصاویر <video poster> و فراداده‌های پیش‌بارگذاری‌شده) درطول بارگیری اولیه صفحه، پهنای باند شبکه و فضای ذخیره‌سازی قابل‌توجهی را مصرف می‌کنند. حتی وقتی preload="metadata" یا preload="none" روی <video> تنظیم شده باشد، مرورگر مشتاقانه تصویر poster آن را فوراً واکشی می‌کند و preload="metadata" همچنان درخواست‌های محدوده را برای سرایندهای رسانه در همه پخش‌کننده‌های صفحه آغاز می‌کند.

افزودن loading="lazy" به عناصر <video> و <audio> خارج از صفحه به مرورگر دستور می‌دهد تا همه درخواست‌های شبکه رسانه را به‌تعویق بیندازد—ازجمله بارگیری‌های تصویر poster، نکات preload، و اجرای autoplay—تا زمانی که عنصر به فاصله محاسبه‌شده توسط مرورگر از نمای دیداری برسد.

نحوه پیاده‌سازی

  1. افزودن loading="lazy" به رسانه خارج از صفحه: loading="lazy" را به عناصر <video> و <audio> که در زیر خط تا قرار دارند اعمال کنید. مشخصه loading را در رسانه بالای خط تا حذف کنید تا به‌طور پیش‌فرض به eager تبدیل شود.
  2. تنظیم صریح width و height در عناصر <video>: همیشه مشخصه‌های عدد صحیح width و height (در پیکسل‌های CSS) را در <video> مشخص کنید. ویدیوهای بارنشده به‌طور پیش‌فرض ابعاد 300×150 دارند؛ بدون اندازه‌گذاری صریح، ممکن است ویدیو بارشده به‌صورت کُند نتواند با نمای دیدنی تقاطع داشته باشد و هرگز بار نشود، یا هنگام بار شدن باعث تغییرات چیدمان مختل‌کننده (CLS) شود.
  3. مشخصه controls را در عناصر <audio> اضافه کنید: همیشه controls را در <audio loading="lazy"> اضافه کنید. چون عناصر <audio> بدون کنترل‌های مرورگر جعبه دیداری ذاتی ندارند، عنصر <audio loading="lazy"> بدون controls نمی‌تواند به‌طور قابل‌مشاهده با نمای هدف تلاقی داشته باشد و هرگز بار نمی‌شود یا به‌طور خودکار پخش نمی‌شود.
  4. مشخصه‌های preload و poster مختصات:
    • در <video loading="lazy">، هم واکشی تصویر poster و هم رفتار preload (none، metadata، یا auto) تا زمانی که ویدیو به نمای هدف نزدیک شود به‌تعویق می‌افتد.
    • وقتی کاربر باید پس‌از پیمایش به رسانه صریحاً روی پخش فشار دهد، loading="lazy" را با preload="none" جفت کنید، یا وقتی مدت و داده‌های فریم اولیه باید به‌طور خودکار پس‌از ورود پخش‌کننده به نمای هدف پر شود، loading="lazy" را با preload="metadata" جفت کنید.
  5. مدیریت ایمن بارگیری تنبل autoplay: وقتی loading="lazy" با autoplay در <video> بی‌صدا (یا <audio controls> موافقت‌شده) ترکیب می‌شود، بازپخش و بارگیری تا زمانی که عنصر به نمای دید نزدیک شود منتظر می‌مانند. همیشه قبل‌از پخش خودکار رسانه حرکتی، @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);
});

روال‌های مطلوب

  • اجباری: همیشه مشخصه‌های صریح width و height را روی عناصر <video> تنظیم کنید. ویدیوهای بارنشده اندازه ذاتی 300×150 دارند و اگر با بخش قابل‌مشاهده صفحه تلاقی نداشته باشند هرگز بار نخواهند شد.
  • اجباری: همیشه مشخصه controls را در عناصر <audio loading="lazy"> اضافه کنید. عناصر <audio> بارشده به‌صورت کُند بدون controls به‌صورت نمایان رندر نمی‌شوند و هرگز بار نمی‌شوند یا به‌طور خودکار پخش نمی‌شوند.
  • loading="lazy" را برای عناصر بالای خط تا یا «بزرگ‌ترین نقاشی دارای محتوا» (LCP) <video> اعمال نکنید، زیرا واکشی داده‌های poster تصویر و ویدیو را به‌تأخیر می‌اندازد.
  • باید مشخصه loading را زمانی که بارگیری مشتاقانه پیش‌فرض مدنظر است حذف کنید، به‌جای اینکه صریحاً loading="eager" را بنویسید.
  • برای تشخیص زمان بار شدن عناصر <video> یا <audio> بارشده به‌صورت کُند، به رویداد window load اتکا نکنید. رویداد load فقط براساس رسانه بارشده مشتاقانه فعال می‌شود، حتی اگر عنصر رسانه loading="lazy" در ابتدا داخل نمای دیداری باشد.
  • توجه داشته باشید که مرورگرها فقط زمانی رسانه loading="lazy" را به‌تعویق می‌اندازند که JavaScript فعال باشد (یک اقدام ضد ردیابی برای جلوگیری از استنباط موقعیت پیمایش توسط سرورها ازطریق درخواست‌های رسانه زمانی که اسکریپت‌نویسی غیرفعال است).

راهبرد جایگزین

اگر هدف «خط پایه» شما از loading="lazy" در <video> و <audio> پشتیبانی نمی‌کند، براساس اینکه پهنای باند چقدر باید به‌طور دقیق حفظ شود، بین ارتقای پیش‌رو و بازگشت IntersectionObserver یکی را انتخاب کنید:

مرورگرهایی که loading="lazy" را در <video> و <audio> تشخیص نمی‌دهند، این مشخصه را نادیده می‌گیرند و به بارگیری استاندارد که توسط مشخصه preload کنترل می‌شود برمی‌گردند. جفت کردن loading="lazy" با preload="none" یا preload="metadata" تضمین می‌کند که مرورگرهای پشتیبانی‌نشده همچنان از بارگیری کامل فایل رسانه در ابتدا اجتناب کنند، هرچند تصاویر <video poster> و سرایندهای preload="metadata" با اشتیاق واکشی خواهند شد.

جایگزین کد سفارشی (IntersectionObserver)

وقتی هدف Baseline شما به‌طور دقیق مستلزم به‌تعویق انداختن بارگیری‌های <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);
  }
}