Отложенная загрузка видео и аудио

Элементы мультимедиа (<video> и <audio>) и связанные с ними ресурсы (например, изображения <video poster> и предварительно загруженные метаданные) потребляют значительную пропускную способность сети и хранилища во время начальной загрузки страницы. Даже если для элемента <video> задано значение preload="metadata" или preload="none", браузер сразу же загружает изображение 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. Если не указать размеры, видео с отложенной загрузкой может не попасть в видимую область просмотра и не загрузиться или вызвать смещение макета при загрузке.
  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", если пользователь должен нажать кнопку воспроизведения после прокрутки до медиаконтента, или с 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" к элементам в верхней части страницы или элементам, связанным с показателем Largest Contentful Paint (LCP) <video>, так как это задерживает получение данных об изображениях и видео poster.
  • РЕКОМЕНДУЕТСЯ не указывать атрибут loading, если вы хотите использовать быструю загрузку по умолчанию, а не задавать ее явным образом с помощью значения loading="eager".
  • НЕ полагайтесь на событие window load, чтобы определить, когда загрузились элементы <video> или <audio> с отложенной загрузкой. Событие 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);
  }
}