Elementy multimedialne (<video> i <audio>) oraz powiązane z nimi zasoby (takie jak obrazy <video poster> i wstępnie załadowane metadane) zużywają znaczną przepustowość sieci i pamięci podczas początkowego wczytywania strony. Nawet jeśli atrybut preload="metadata" lub preload="none" jest ustawiony w elemencie <video>, przeglądarka natychmiast pobiera obraz poster, a atrybut preload="metadata" nadal inicjuje żądania zakresu dotyczące nagłówków multimediów we wszystkich odtwarzaczach na stronie.
Dodanie atrybutu loading="lazy" do elementów <video> i <audio> poza ekranem powoduje, że przeglądarka odracza wszystkie żądania sieciowe dotyczące multimediów – w tym pobieranie obrazów poster, wskazówki preload i wykonywanie autoplay – dopóki element nie osiągnie obliczonej przez przeglądarkę odległości od widocznego obszaru.
Jak to zrobić?
- Dodaj
loading="lazy"do multimediów poza ekranem: zastosujloading="lazy"do elementów<video>i<audio>umieszczonych w części strony widocznej po przewinięciu. Pomiń atrybutloadingw przypadku multimediów widocznych na ekranie, aby domyślnie ustawić wartośćeager. - Ustaw jawne wartości
widthiheightw elementach<video>: zawsze określaj atrybutywidthiheight(w pikselach CSS) jako liczby całkowite w elementach<video>. Niewczytane filmy mają domyślnie wymiary300×150. Bez wyraźnego określenia rozmiaru film ładowany leniwie może nie przecinać widocznego obszaru i nigdy się nie wczytać lub powodować uciążliwe przesunięcia układu (CLS) podczas wczytywania. - Uwzględnij atrybut
controlsw elementach<audio>: zawsze uwzględniaj atrybutcontrolsw elementach<audio loading="lazy">. Elementy<audio>nie mają wewnętrznego pola wizualnego bez elementów sterujących przeglądarki, więc element<audio loading="lazy">bezcontrolsnie może w widoczny sposób przecinać widocznego obszaru i nigdy nie zostanie wczytany ani automatycznie odtworzony. - Atrybuty współrzędnych
preloadiposter:- W przypadku
<video loading="lazy">zarówno pobieranie obrazuposter, jak i działaniepreload(none,metadatalubauto) są odroczone do momentu, gdy film zbliży się do widocznego obszaru. - Połącz
loading="lazy"zpreload="none", gdy użytkownik musi wyraźnie nacisnąć przycisk odtwarzania po przewinięciu do multimediów, lub zpreload="metadata", gdy dane o czasie trwania i początkowej klatce powinny być wypełniane automatycznie po wejściu odtwarzacza do widocznego obszaru.
- W przypadku
- Bezpieczne korzystanie z atrybutu
autoplay: gdy atrybutloading="lazy"jest połączony z atrybutemautoplayw wyciszonym elemencie<video>(lub elemencie<audio controls>z opcją akceptacji), odtwarzanie i pobieranie są wstrzymywane, dopóki element nie zbliży się do widocznego obszaru. Zawsze przestrzegaj ustawienia@media (prefers-reduced-motion: reduce)przed automatycznym odtwarzaniem multimediów ruchomych.
Przykładowy 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);
});
Sprawdzone metody
- OBOWIĄZKOWE: zawsze ustawiaj jawne atrybuty
widthiheightw elementach<video>. Niewczytane filmy mają rozmiar wewnętrzny300×150i nigdy się nie wczytają, jeśli nie przecinają widocznej części strony. - OBOWIĄZKOWO: zawsze dodawaj atrybut
controlsdo elementów<audio loading="lazy">. Elementy<audio>ładowane leniwie bez atrybutucontrolsnie są renderowane w widoczny sposób i nigdy nie zostaną załadowane ani automatycznie odtworzone. - NIE stosuj atrybutu
loading="lazy"do elementów widocznych w górnej części strony ani do elementów największego wyrenderowania treści (LCP)<video>, ponieważ opóźnia to pobieranie danych zarówno obrazu, jak i filmuposter. - POMIŃ atrybut
loading, jeśli chcesz użyć domyślnego ładowania natychmiastowego, zamiast jawnie wpisywaćloading="eager". - NIE polegaj na zdarzeniu
windowload, aby wykrywać, kiedy elementy<video>lub<audio>zostały wczytane metodą leniwego ładowania. Zdarzenieloadjest wywoływane tylko na podstawie multimediów wczytywanych od razu, nawet jeśli element multimedialnyloading="lazy"znajduje się początkowo w widocznym obszarze. - PAMIĘTAJ, że przeglądarki odraczają ładowanie multimediów
loading="lazy"tylko wtedy, gdy JavaScript jest włączony (jest to środek zapobiegający śledzeniu, który uniemożliwia serwerom wnioskowanie o pozycji przewijania na podstawie żądań multimediów, gdy skrypty są wyłączone).
Strategia zastępcza
Jeśli Twój cel Baseline nie obsługuje loading="lazy" na <video> i <audio>, wybierz między progresywnym ulepszaniem a IntersectionObserver kreacją zastępczą, biorąc pod uwagę, jak rygorystycznie należy oszczędzać przepustowość:
Progresywne ulepszanie (zalecane ustawienie domyślne)
Przeglądarki, które nie rozpoznają atrybutu loading="lazy" w tagach <video> i <audio>, ignorują go i wracają do standardowego wczytywania, którym zarządza atrybut preload. Połączenie loading="lazy" z preload="none" lub preload="metadata" sprawia, że nieobsługiwane przeglądarki nie pobierają od razu całego pliku multimedialnego, chociaż obrazy <video poster> i nagłówki preload="metadata" będą pobierane z wyprzedzeniem.
Zastępczy kod niestandardowy (IntersectionObserver)
Jeśli Twój cel Baseline wymaga ścisłego odroczenia pobierania <video poster>, żądań preload="metadata" lub odtwarzania wywoływanego przez widoczny obszar w przeglądarkach bez natywnej obsługi, wykryj funkcję Object.hasOwn(HTMLMediaElement.prototype, 'loading'), a następnie użyj 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);
}
}