Imagens costumam ser o maior componente do peso de uma página e, ao mesmo tempo, um dos recursos mais “sensíveis” para a percepção do usuário: elas definem o conteúdo visual, influenciam o LCP quando estão acima da dobra e podem competir com CSS/JS por banda e prioridade. Neste capítulo, o foco é controlar quando e com que prioridade as imagens são carregadas, usando lazy loading, preload seletivo e prioridade de recursos (incluindo dicas de como evitar armadilhas comuns).
Lazy loading: carregar imagens só quando fizer sentido
Lazy loading é a estratégia de adiar o carregamento de imagens que não são necessárias no primeiro momento (tipicamente, abaixo da dobra), reduzindo trabalho de rede e decodificação no início. O objetivo é liberar banda e CPU para o que realmente impacta a primeira renderização e a interação inicial.
Quando usar (e quando evitar)
- Use para imagens abaixo da dobra, galerias longas, listas de cards, feeds e páginas com muitas imagens.
- Evite aplicar lazy loading no “hero” (imagem principal acima da dobra) ou em imagens que você espera que apareçam imediatamente ao abrir a página. Nesses casos, o atraso pode piorar o LCP e a percepção de velocidade.
- Cuidado com elementos que entram rapidamente na viewport (por exemplo, um banner logo abaixo do hero). Se o usuário rolar pouco, o atraso pode ser perceptível.
Lazy loading nativo com loading="lazy"
O caminho mais simples é usar o lazy loading nativo do navegador:
<img src="/img/produto-123.jpg" alt="Produto" loading="lazy" width="320" height="320">Pontos importantes:
- Defina width e height (ou CSS equivalente) para evitar mudanças de layout quando a imagem carregar.
- Não use loading="lazy" em imagens críticas acima da dobra.
- Combine com srcset/sizes quando aplicável, para o navegador escolher a melhor variante (o lazy loading não substitui a seleção responsiva).
Lazy loading com IntersectionObserver (controle fino)
O lazy loading nativo é ótimo, mas às vezes você precisa de controle: pré-carregar um pouco antes de entrar na viewport, lidar com placeholders, ou aplicar regras diferentes por componente. Um padrão comum é usar data-src e trocar para src quando o elemento estiver próximo de aparecer.
Continue em nosso aplicativo
Você poderá ouvir o audiobook com a tela desligada, ganhar gratuitamente o certificado deste curso e ainda ter acesso a outros 5.000 cursos online gratuitos.
ou continue lendo abaixo...Baixar o aplicativo
<img data-src="/img/galeria-01.jpg" alt="Foto" width="800" height="600" class="lazy">const images = document.querySelectorAll('img.lazy'); const io = new IntersectionObserver((entries, observer) => { for (const entry of entries) { if (!entry.isIntersecting) continue; const img = entry.target; img.src = img.dataset.src; img.removeAttribute('data-src'); img.classList.remove('lazy'); observer.unobserve(img); } }, { rootMargin: '200px 0px', threshold: 0.01 }); images.forEach(img => io.observe(img));Detalhes que fazem diferença:
- rootMargin permite começar o download antes do elemento entrar na viewport, reduzindo “piscadas” ao rolar.
- Evite observar centenas/milhares de elementos de uma vez em páginas muito longas. Em listas grandes, combine com virtualização (renderizar apenas itens próximos) para reduzir custo de DOM e observadores.
- Fallback: se você precisa suportar ambientes sem IntersectionObserver, use
loading="lazy"como fallback ou carregue tudo (dependendo do público).
Placeholders e transição sem “salto”
Mesmo com dimensões definidas, é comum querer um placeholder (cor sólida, blur, skeleton) para evitar “buracos” visuais. Um padrão simples é usar uma cor de fundo e uma transição de opacidade quando a imagem carregar:
<img data-src="/img/foto.jpg" alt="Foto" width="800" height="600" class="lazy fade">img.fade { background: #f2f2f2; opacity: 0; transition: opacity 200ms ease; } img.fade.loaded { opacity: 1; }img.addEventListener('load', () => img.classList.add('loaded'), { once: true });Isso melhora a percepção durante o scroll sem introduzir mudanças de layout.
Preload seletivo: acelerar o que é crítico (sem exagerar)
Preload é um “sinal” para o navegador iniciar o download de um recurso cedo, antes mesmo de ele ser descoberto naturalmente pelo parser. Em imagens, isso é útil quando a imagem é crítica (por exemplo, hero) e você quer reduzir o tempo até ela estar disponível para renderização.
O ponto-chave é ser seletivo: preloading demais pode competir com CSS/JS e piorar o carregamento geral. A regra prática: preload apenas 1 (no máximo 2) imagens realmente críticas por rota, e valide se isso melhora o tempo do elemento principal.
Preload de imagem no HTML
Você pode adicionar no <head>:
<link rel="preload" as="image" href="/img/hero.jpg">Quando a imagem é responsiva (varia por viewport), use imagesrcset e imagesizes para o navegador pré-carregar a variante correta:
<link rel="preload" as="image" href="/img/hero-1280.jpg" imagesrcset="/img/hero-640.jpg 640w, /img/hero-1280.jpg 1280w, /img/hero-1920.jpg 1920w" imagesizes="(max-width: 768px) 100vw, 1200px">Sem isso, você corre o risco de pré-carregar uma imagem que não será usada (desperdício de banda).
Preload e cache: evite download duplicado
Um erro comum é pré-carregar uma URL e, no <img>, usar outra URL (por exemplo, com parâmetros diferentes, domínio diferente, ou variação de tamanho diferente). Para o navegador reaproveitar o preload, a URL final precisa corresponder ao recurso efetivamente usado (mesmo href e mesmas condições de seleção).
Checklist rápido para evitar duplicação:
- O
hrefdo preload é exatamente a mesma URL que será usada. - Se houver
srcset/sizes, useimagesrcset/imagesizesno preload. - Se a imagem vem de outro domínio, garanta que CORS e headers permitam cache compartilhado quando necessário.
Preload condicional por rota ou template
Em aplicações com múltiplas rotas, o ideal é preloading baseado em contexto: só pré-carregar o hero da rota atual, não o de todas. Em SSR/templating, isso costuma ser simples: o template da página injeta o preload apenas quando aquele componente está presente acima da dobra.
Exemplo conceitual (pseudo-template):
{{#if hasHero}} <link rel="preload" as="image" href="{{heroUrl}}" imagesrcset="{{heroSrcset}}" imagesizes="{{heroSizes}}"> {{/if}}Prefetch vs preload (não confundir)
Preload é para recursos necessários agora. Prefetch é para recursos que podem ser úteis depois (por exemplo, próxima rota). Para imagens, prefetch pode ser interessante em navegação previsível (ex.: ao passar o mouse em um card que leva a uma página de produto), mas deve ser usado com cuidado para não consumir banda em conexões lentas.
Prioridade de recursos: dizer ao navegador o que vem primeiro
Mesmo com lazy loading e preload, o navegador ainda precisa decidir como priorizar downloads concorrentes. A prioridade influencia quem “ganha” a banda e quem começa antes. Para imagens, você pode atuar em três frentes: atributos de prioridade, ordem de descoberta e evitar competição desnecessária.
fetchpriority: alta prioridade para a imagem certa
O atributo fetchpriority permite indicar ao navegador a prioridade relativa de um recurso. Para a imagem principal acima da dobra, pode fazer sentido:
<img src="/img/hero.jpg" alt="" width="1200" height="600" fetchpriority="high">Para imagens abaixo da dobra, você pode reforçar baixa prioridade (especialmente se não estiver usando lazy loading nativo por algum motivo):
<img src="/img/galeria-20.jpg" alt="" width="800" height="600" fetchpriority="low" loading="lazy">Boas práticas:
- Use high com parcimônia: normalmente apenas a imagem principal (ou uma imagem crítica adicional, se houver).
- Não marque tudo como high, senão você perde o benefício e pode prejudicar CSS/JS e fontes.
- Combine com preload quando a imagem é descoberta tarde (por exemplo, se o HTML é gerado após algum script). Se a imagem já está cedo no HTML, às vezes
fetchprioritybasta.
Ordem no HTML e descoberta precoce
O navegador só pode priorizar o que ele já descobriu. Se a imagem crítica aparece tarde no HTML (ou depende de JS para ser inserida), ela começa a baixar tarde. Estratégias:
- Coloque a imagem crítica cedo no HTML quando possível (SSR ajuda).
- Evite inserir o hero via JS após o carregamento inicial. Se for inevitável, use preload para “adiantar” o download.
- Evite CSS background-image para imagens críticas, porque a descoberta pode ocorrer mais tarde (após CSS ser baixado e processado). Se precisar, considere preload do asset.
Imagens como background: como não perder prioridade
Quando uma imagem é aplicada via CSS (background-image), ela não tem loading, fetchpriority ou srcset nativos. Para backgrounds críticos, duas abordagens comuns:
- Trocar para <img> quando a imagem é conteúdo (não apenas decoração), ganhando controle de prioridade e responsividade.
- Manter como background, mas usar
<link rel="preload" as="image">para garantir descoberta precoce.
Exemplo de preload para background:
<link rel="preload" as="image" href="/img/hero-bg.jpg">Evitar competição: não deixe imagens “roubarem” banda do crítico
Mesmo com lazy loading, algumas imagens podem começar cedo por estarem próximas da dobra, por estarem em carrosséis, ou por serem descobertas antes do que você imagina. Para reduzir competição:
- Lazy load agressivo em listas longas e seções abaixo da dobra.
- Adie carrosséis: muitos carrosséis carregam várias imagens “do primeiro slide” e também pré-carregam slides seguintes. Configure para carregar apenas o slide visível e, no máximo, o próximo.
- Evite placeholders pesados: um placeholder em base64 gigante ou um SVG complexo pode competir com o crítico. Prefira placeholders leves.
- Controle de terceiros: widgets que injetam imagens (reviews, ads, social) podem iniciar downloads cedo. Sempre que possível, carregue esses widgets após a estabilização do conteúdo principal.
Passo a passo prático: implementar uma estratégia completa por tipo de imagem
Passo 1: classificar imagens por criticidade
Antes de codar, classifique:
- Críticas: acima da dobra, essenciais para entender a página (ex.: hero, imagem principal do produto).
- Quase-críticas: aparecem logo após pequena rolagem (ex.: primeira fileira de cards).
- Não-críticas: galerias longas, imagens de rodapé, recomendações, conteúdo “abaixo”.
Passo 2: aplicar preload e prioridade nas críticas
Para a imagem crítica principal:
- Adicionar
<link rel="preload" as="image" ...>no head (comimagesrcset/imagesizesse responsiva). - No
<img>, usarfetchpriority="high". - Não usar
loading="lazy".
<!-- no head --> <link rel="preload" as="image" href="/img/hero-1280.jpg" imagesrcset="/img/hero-640.jpg 640w, /img/hero-1280.jpg 1280w, /img/hero-1920.jpg 1920w" imagesizes="(max-width: 768px) 100vw, 1200px"> <!-- no body --> <img src="/img/hero-1280.jpg" srcset="/img/hero-640.jpg 640w, /img/hero-1280.jpg 1280w, /img/hero-1920.jpg 1920w" sizes="(max-width: 768px) 100vw, 1200px" width="1200" height="600" alt="" fetchpriority="high">Passo 3: lazy load nas não-críticas (nativo primeiro)
Para imagens abaixo da dobra, comece com o nativo:
<img src="/img/card-01.jpg" alt="Item" width="360" height="240" loading="lazy" fetchpriority="low">Se você notar atraso perceptível ao rolar, migre para IntersectionObserver com rootMargin maior (por exemplo, 200–400px) para iniciar o download antes.
Passo 4: tratar “quase-críticas” com estratégia híbrida
Imagens logo após a dobra podem se beneficiar de:
- loading="lazy" (muitas vezes suficiente).
- IntersectionObserver com rootMargin para começar um pouco antes.
- Evitar preload aqui na maioria dos casos, para não competir com o crítico.
Exemplo com IO e pré-carregamento antecipado via margem:
const io = new IntersectionObserver((entries, observer) => { for (const entry of entries) { if (!entry.isIntersecting) continue; const img = entry.target; img.src = img.dataset.src; observer.unobserve(img); } }, { rootMargin: '400px 0px' });Passo 5: carrosséis e galerias: carregar só o necessário
Carrosséis frequentemente são vilões porque carregam múltiplas imagens “por precaução”. Uma abordagem prática:
- Carregar imediatamente apenas a imagem do slide visível.
- Pré-carregar apenas o próximo slide quando o usuário interagir (ex.: ao tocar/arrastar) ou quando o carrossel estiver visível.
- Lazy load para os demais slides.
function preloadImage(url) { const img = new Image(); img.decoding = 'async'; img.src = url; } // Ao detectar que o carrossel entrou na viewport: preloadImage(nextSlideUrl);Armadilhas comuns e como evitar
Lazy loading em excesso no topo
Aplicar loading="lazy" globalmente em todas as imagens parece tentador, mas costuma atrasar imagens importantes. Faça exceções explícitas para o conteúdo acima da dobra e para imagens que precisam aparecer imediatamente.
Preload demais
Preload é poderoso, mas caro em termos de competição. Se você pré-carrega várias imagens, o navegador pode dividir banda entre elas e atrasar recursos essenciais. Mantenha o preload restrito ao que é realmente crítico.
URLs diferentes entre preload e uso real
Se o preload aponta para /hero.jpg mas o <img> usa /hero.jpg?v=2 ou outra variante, você pode acabar com dois downloads. Garanta consistência de URL e de seleção responsiva.
Imagens injetadas por JS sem estratégia de prioridade
Quando imagens são inseridas após a carga inicial (por exemplo, renderização client-side tardia), elas começam a baixar tarde. Para imagens críticas, prefira SSR ou preload. Para não-críticas, use lazy loading com IO.
Decodificação e custo de renderização durante scroll
Mesmo quando a rede está ok, decodificar e pintar muitas imagens durante o scroll pode causar travadinhas. Mitigações práticas:
- Use
decoding="async"em imagens não-críticas para reduzir bloqueios de renderização. - Evite disparar muitos loads ao mesmo tempo: ajuste
rootMargine considere carregar em “lotes” em listas muito longas.
<img src="/img/feed-99.jpg" alt="" width="600" height="400" loading="lazy" decoding="async">Checklist de implementação rápida
- Imagem principal acima da dobra: sem lazy, fetchpriority="high", preload seletivo (idealmente 1), dimensões definidas.
- Imagens abaixo da dobra: loading="lazy" e, se necessário, IntersectionObserver com rootMargin para suavizar.
- Carrosséis: carregar só o slide visível; pré-carregar o próximo sob condição; lazy nos demais.
- Background crítico: considerar preload ou trocar para
<img>quando fizer sentido. - Evitar duplicação: URLs e variantes responsivas consistentes entre preload e
<img>.