Media-elementen (<video> en <audio>) en de bijbehorende resources (zoals <video poster>-afbeeldingen en vooraf geladen metadata) verbruiken aanzienlijke netwerk- en opslagbandbreedte tijdens het laden van de pagina. Zelfs als preload="metadata" of preload="none" is ingesteld op een <video>, haalt de browser de poster-afbeelding onmiddellijk op en start preload="metadata" nog steeds bereikverzoeken voor mediakoppen voor elke speler op de pagina.
Als je loading="lazy" toevoegt aan elementen buiten het scherm <video> en <audio>, krijgt de browser de instructie om alle netwerkverzoeken voor media uit te stellen, waaronder poster downloads van afbeeldingen, preload hints en autoplay uitvoering, totdat het element een door de browser berekende afstand tot de visuele viewport bereikt.
Hoe wordt dit geïmplementeerd
- Voeg
loading="lazy"toe aan media buiten het scherm: Pasloading="lazy"toe op<video>- en<audio>-elementen die onder de vouw zijn geplaatst. Laat het kenmerkloadingweg bij media boven de vouw, zodat de standaardwaardeeagerwordt gebruikt. - Stel expliciete
widthenheightin voor<video>-elementen: Geef altijd gehelewidth- enheight-kenmerken (in css-pixels) op voor<video>. Niet-geladen video's hebben standaard de afmetingen300×150. Zonder expliciete afmetingen kan een lazy-loaded video de zichtbare viewport misschien niet kruisen en nooit worden geladen, of storende verschuivingen in de vormgeving (CLS) veroorzaken wanneer deze wordt geladen. - Neem het kenmerk
controlsop in<audio>-elementen: Neemcontrolsaltijd op in<audio loading="lazy">. Omdat<audio>-elementen geen intrinsiek visueel vak hebben zonder browserbedieningselementen, kan een<audio loading="lazy">-element zondercontrolsde viewport niet zichtbaar kruisen en wordt het nooit geladen of automatisch afgespeeld. - Kenmerken voor coördinaten
preloadenposter:- Op
<video loading="lazy">worden zowel het ophalen van deposter-afbeelding als hetpreload-gedrag (none,metadataofauto) uitgesteld totdat de video de viewport nadert. - Koppel
loading="lazy"aanpreload="none"als de gebruiker expliciet op Afspelen moet drukken nadat die naar de media heeft gescrold, of aanpreload="metadata"als de duur en de gegevens van het eerste frame automatisch moeten worden ingevuld zodra de speler de viewport binnengaat.
- Op
- Lazy
autoplayveilig verwerken: Alsloading="lazy"wordt gecombineerd metautoplayop een gedempte<video>(of een opt-in<audio controls>), wordt het afspelen en downloaden uitgesteld totdat het element de viewport nadert. Respecteer altijd@media (prefers-reduced-motion: reduce)voordat je bewegingsmedia automatisch afspeelt.
Voorbeeldcode
<!-- 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);
});
Praktische tips
- VERPLICHT: Stel altijd expliciete kenmerken voor
widthenheightin voor<video>-elementen. Niet-geladen video's hebben een intrinsieke grootte van300×150en worden nooit geladen als ze geen zichtbaar deel van de pagina overlappen. - VERPLICHT: Neem het kenmerk
controlsaltijd op in<audio loading="lazy">-elementen. Lazy-loaded<audio>-elementen zondercontrolsworden niet zichtbaar weergegeven en worden nooit geladen of automatisch afgespeeld. - Pas
loading="lazy"NIET toe op elementen boven de vouw of Largest Contentful Paint (LCP)<video>, omdat dit het ophalen van zowel deposterafbeeldings- als videogegevens vertraagt. - WEL: Laat het kenmerk
loadingweg als standaard eager loading gewenst is in plaats van explicietloading="eager"te schrijven. - Vertrouw NIET op de gebeurtenis
windowloadom te detecteren wanneer lazy-loaded<video>- of<audio>-elementen zijn geladen. De gebeurtenisloadwordt alleen geactiveerd op basis van eager-loaded media, zelfs als een media-elementloading="lazy"zich in eerste instantie in de visuele viewport bevindt. - Houd er rekening mee dat browsers
loading="lazy"-media alleen uitstellen als JavaScript aanstaat (een maatregel tegen tracking om te voorkomen dat servers de scrollpositie afleiden via mediaverzoeken als scripting uitstaat).
Reservestrategie
Als je basislijndoel loading="lazy" niet ondersteunt op <video> en <audio>, kies je tussen progressive enhancement en een IntersectionObserver-fallback op basis van hoe strikt de bandbreedte moet worden bespaard:
Progressive enhancement (aanbevolen standaard)
Browsers die loading="lazy" op <video> en <audio> niet herkennen, negeren het kenmerk en vallen terug op standaard laden dat wordt beheerd door het kenmerk preload. Als je loading="lazy" koppelt aan preload="none" of preload="metadata", voorkom je dat niet-ondersteunde browsers het hele mediabestand vooraf downloaden, hoewel <video poster>-afbeeldingen en preload="metadata"-headers gretig worden opgehaald.
Reserve voor aangepaste code (IntersectionObserver)
Als je Baseline-doel vereist dat <video poster>-downloads, preload="metadata"-verzoeken of door de viewport geactiveerde weergave strikt worden uitgesteld in browsers zonder systeemeigen ondersteuning, voer je functiedetectie uit voor Object.hasOwn(HTMLMediaElement.prototype, 'loading') en val je terug op 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);
}
}