Os elementos de mídia (<video> e <audio>) e os recursos associados (como imagens <video poster> e metadados pré-carregados) consomem uma largura de banda significativa de rede e armazenamento durante o carregamento de página inicial. Mesmo quando preload="metadata" ou preload="none" é definido em um <video>, o navegador busca imediatamente a imagem poster, e preload="metadata" ainda inicia solicitações de intervalo para cabeçalhos de mídia em todos os players da página.
Adicionar loading="lazy" aos elementos <video> e <audio> fora da tela instrui o navegador a adiar todas as solicitações de rede de mídia, incluindo downloads de imagens poster, dicas de preload e execução de autoplay, até que o elemento alcance uma distância calculada pelo navegador da janela de visualização.
Como fazer a implementação
- Adicione
loading="lazy"à mídia fora da tela: apliqueloading="lazy"aos elementos<video>e<audio>posicionados abaixo da dobra. Omita o atributoloadingna mídia acima da dobra para que o padrão sejaeager. - Defina
widtheheightexplícitos em elementos<video>: sempre especifique os atributos inteiroswidtheheight(em pixels CSS) em<video>. Os vídeos não carregados têm dimensões padrão de300×150. Sem um dimensionamento explícito, um vídeo com carregamento lento pode não se cruzar com a janela de visualização visível e nunca carregar, ou causar mudanças cumulativas de layout (CLS) ao carregar. - Inclua o atributo
controlsnos elementos<audio>: sempre incluacontrolsem<audio loading="lazy">. Como os elementos<audio>não têm uma caixa visual intrínseca sem controles do navegador, um elemento<audio loading="lazy">semcontrolsnão pode cruzar visivelmente a janela de visualização e nunca será carregado ou reproduzido automaticamente. - Atributos de coordenadas
preloadeposter:- No
<video loading="lazy">, a busca de imagenspostere o comportamentopreload(none,metadataouauto) são adiados até que o vídeo se aproxime da janela de visualização. - Combine
loading="lazy"compreload="none"quando o usuário precisar pressionar explicitamente o botão de reprodução depois de rolar até a mídia oupreload="metadata"quando os dados de duração e frame inicial precisarem ser preenchidos automaticamente assim que o player entrar na janela de visualização.
- No
- Processar
autoplaylento com segurança: quandoloading="lazy"é combinado comautoplayem um<video>silenciado (ou um<audio controls>de ativação), a reprodução e o download aguardam até que o elemento se aproxime da janela de visualização. Sempre respeite@media (prefers-reduced-motion: reduce)antes de reproduzir automaticamente mídia em movimento.
Exemplo de código
<!-- 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);
});
Práticas recomendadas
- OBRIGATÓRIO: sempre defina atributos
widtheheightexplícitos em elementos<video>. Os vídeos não carregados têm um tamanho intrínseco de300×150e nunca serão carregados se não cruzarem uma parte visível da página. - OBRIGATÓRIO: sempre inclua o atributo
controlsnos elementos<audio loading="lazy">. Os elementos<audio>de carregamento lento semcontrolsnão são renderizados de forma visível e nunca serão carregados ou reproduzidos automaticamente. - NÃO aplique
loading="lazy"a elementos acima da dobra ou de Largest Contentful Paint (LCP)<video>, porque isso atrasa a busca de dados de imagem e vídeo doposter. - OMITA o atributo
loadingquando o carregamento antecipado padrão for desejado, em vez de escrever explicitamenteloading="eager". - NÃO use o evento
windowloadpara detectar quando os elementos<video>ou<audio>de carregamento lento foram carregados. O eventoloadé acionado com base apenas em mídia carregada de forma antecipada, mesmo que um elemento de mídialoading="lazy"esteja inicialmente dentro da janela de visualização. - OBSERVE que os navegadores só adiam a mídia
loading="lazy"quando o JavaScript está ativado (uma medida de combate ao rastreamento para impedir que os servidores infiram a posição de rolagem por solicitações de mídia quando o script está desativado).
Estratégia de fallback
Se o Baseline de destino não for compatível com loading="lazy" em <video> e <audio>, escolha entre o aprimoramento progressivo e um substituto de IntersectionObserver com base no rigor da conservação da largura de banda:
Aprimoramento progressivo (padrão recomendado)
Os navegadores que não reconhecem loading="lazy" em <video> e <audio> ignoram o atributo e usam o carregamento padrão regido pelo atributo preload. Ao combinar loading="lazy" com preload="none" ou preload="metadata", os navegadores sem suporte ainda evitam o download do arquivo de mídia completo, embora as imagens <video poster> e os cabeçalhos preload="metadata" sejam buscados com antecedência.
Fallback de código personalizado (IntersectionObserver)
Quando sua meta de Baseline exige o adiamento estrito de downloads de <video poster>, solicitações de preload="metadata" ou reprodução acionada pela janela de visualização em navegadores sem suporte nativo, use a detecção de recursos para Object.hasOwn(HTMLMediaElement.prototype, 'loading') e, em seguida, use IntersectionObserver como alternativa:
// 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);
}
}