Capa do Ebook gratuito Performance Front-End: Otimizando Core Web Vitals sem Mistério

Performance Front-End: Otimizando Core Web Vitals sem Mistério

Novo curso

19 páginas

Entrega de imagens responsivas com srcset e decisões por viewport

Capítulo 8

Tempo estimado de leitura: 11 minutos

+ Exercício

Entregar imagens responsivas significa enviar para cada usuário uma versão de imagem adequada ao tamanho em que ela será exibida e à densidade de pixels do dispositivo, evitando dois problemas comuns: baixar uma imagem grande demais (desperdício de bytes e tempo) ou pequena demais (perda de nitidez). O mecanismo central para isso no HTML é a combinação de srcset, sizes e, quando necessário, <picture> com <source>. O navegador escolhe automaticamente o melhor candidato com base no viewport, no layout calculado (CSS) e no DPR (device pixel ratio).

O que o navegador decide quando vê uma imagem responsiva

Ao encontrar um <img> com srcset, o navegador precisa responder a duas perguntas: (1) qual será a largura de exibição da imagem no layout (em CSS pixels)? (2) qual densidade de pixels precisa ser atendida (1x, 2x, 3x etc.)? A partir disso ele calcula uma “largura necessária” em pixels físicos e escolhe o arquivo mais apropriado dentre os candidatos disponíveis.

Existem dois modos principais de declarar candidatos no srcset:

  • Descritores de largura (320w, 640w, 1200w): você fornece várias larguras reais de arquivo. O navegador usa sizes para estimar a largura de exibição e então escolhe o melhor arquivo.
  • Descritores de densidade (1x, 2x): você fornece variações para a mesma largura de exibição, mudando apenas a densidade. É útil quando a imagem sempre ocupa o mesmo tamanho no layout.

Na prática, para layouts fluidos e responsivos, o modo com descritores de largura é o mais flexível, porque o tamanho de exibição muda com o viewport.

srcset com descritores de largura (w): o padrão para decisões por viewport

Quando você usa w, o navegador precisa saber quanto a imagem vai medir na tela. É aí que entra o atributo sizes, que descreve (em termos de viewport) qual será a largura do elemento <img> no layout. O navegador avalia as media conditions em sizes e escolhe a primeira que casar; se nenhuma casar, usa o último valor como fallback.

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...
Download App

Baixar o aplicativo

Exemplo base: imagem que ocupa 100% da largura no mobile e 50% no desktop

<img> src="/img/produto-640.jpg" alt="Tênis em fundo neutro" width="800" height="600" loading="lazy" decoding="async" srcset="/img/produto-320.jpg 320w, /img/produto-480.jpg 480w, /img/produto-640.jpg 640w, /img/produto-800.jpg 800w, /img/produto-1200.jpg 1200w, /img/produto-1600.jpg 1600w" sizes="(min-width: 1024px) 50vw, 100vw" />

Como o navegador decide aqui:

  • Se o viewport tiver pelo menos 1024px, a imagem será exibida com aproximadamente 50vw (metade da largura da janela).
  • Se for menor que 1024px, a imagem será exibida com 100vw (toda a largura da janela).
  • Depois, ele multiplica essa largura pelo DPR (por exemplo, 2 em telas “retina”) e escolhe o candidato mais próximo acima (ou o melhor custo/benefício) dentre os listados.

Esse padrão é o que normalmente se chama de “decisões por viewport”: você descreve o comportamento do layout em função do viewport, e o navegador escolhe o arquivo.

sizes: como escrever corretamente (e como errar menos)

O sizes não é “o tamanho da imagem”, e sim uma declaração do tamanho de exibição no layout. Ele deve refletir o CSS real. Se você errar, o navegador pode baixar um arquivo maior do que o necessário (quando você declara um tamanho maior do que o real) ou menor (quando declara menor e a imagem fica borrada em DPR alto).

Regra prática

  • Se a imagem é fluida e ocupa a largura do container, descreva o container em termos de viewport: 100vw, 50vw, 33vw, etc.
  • Se há gutters/margens, desconte com calc().
  • Se há um max-width no container, reflita isso com uma condição.

Exemplo com container centralizado e max-width

Suponha um layout em que o conteúdo tem max-width: 1100px e padding lateral de 24px. A imagem ocupa 100% da coluna de conteúdo.

<img src="/img/banner-800.jpg" alt="Banner do produto" width="1600" height="600" srcset="/img/banner-480.jpg 480w, /img/banner-800.jpg 800w, /img/banner-1200.jpg 1200w, /img/banner-1600.jpg 1600w, /img/banner-2000.jpg 2000w" sizes="(min-width: 1148px) 1100px, calc(100vw - 48px)" />

Por que 1148px? Porque 1100px de conteúdo + 48px de padding total (24px de cada lado) dá 1148px. A partir desse viewport, a imagem não cresce mais; então faz sentido declarar um tamanho fixo em pixels. Abaixo disso, ela é 100vw - 48px.

Erros comuns em sizes

  • Usar sempre 100vw mesmo quando a imagem está em uma coluna estreita. Isso faz o navegador escolher arquivos grandes demais.
  • Esquecer que vw considera a largura total do viewport, não a do container. Se o container tem margens/padding, use calc() ou condições.
  • Declarar tamanhos fixos sem refletir breakpoints. Se o layout muda em 768px e 1024px, o sizes geralmente deve mudar também.

srcset com descritores de densidade (x): quando faz sentido

O modo 1x/2x é útil quando o tamanho de exibição é essencialmente constante (por exemplo, um avatar de 48px, um ícone de marca em um header, uma miniatura com dimensões fixas). Nesse caso, você não precisa de sizes.

Exemplo: avatar fixo

<img src="/img/avatar-48.jpg" alt="Foto do autor" width="48" height="48" srcset="/img/avatar-48.jpg 1x, /img/avatar-96.jpg 2x" />

O navegador escolhe 2x em DPR 2, e 1x em DPR 1. Se o avatar for exibido em tamanhos diferentes em breakpoints, volte ao modo w com sizes.

picture e source: decisões por viewport e direção de arte

srcset em <img> resolve bem o caso de “mesma imagem, diferentes tamanhos”. Mas há situações em que você quer mudar o recorte, a composição ou até a proporção conforme o viewport (por exemplo, um banner horizontal no desktop e um recorte vertical no mobile). Isso é “direção de arte” e é o caso clássico para <picture>.

Exemplo: recorte diferente no mobile

<picture> <source media="(max-width: 600px)" srcset="/img/hero-mobile-400.jpg 400w, /img/hero-mobile-800.jpg 800w" sizes="100vw" /> <source media="(min-width: 601px)" srcset="/img/hero-desktop-800.jpg 800w, /img/hero-desktop-1200.jpg 1200w, /img/hero-desktop-1600.jpg 1600w" sizes="100vw" /> <img src="/img/hero-desktop-1200.jpg" alt="Pessoa usando o app em um notebook" width="1600" height="900" /> </picture>

Como funciona:

  • O navegador avalia os <source> na ordem e escolhe o primeiro cujo media casa.
  • Dentro do <source> escolhido, ele aplica a lógica de srcset + sizes para selecionar o arquivo.
  • O <img> final serve como fallback e também carrega atributos como alt, width e height.

Passo a passo prático: implementar srcset + sizes com base no layout real

1) Meça o tamanho de exibição em cada breakpoint

Você precisa saber quanto a imagem realmente ocupa no layout. Um método prático é inspecionar o elemento em diferentes larguras de viewport e anotar a largura renderizada (em CSS pixels). Faça isso para os breakpoints que importam (por exemplo, 360, 768, 1024, 1440).

Exemplo de cenário: uma página de artigo com imagem principal dentro de um container central. A imagem ocupa 100% do container, e o container tem:

  • Até 768px: largura = 100vw - 32px (padding 16px de cada lado)
  • De 769px até 1200px: largura = 720px
  • Acima de 1200px: largura = 960px

2) Escreva sizes refletindo essas regras

sizes="(min-width: 1200px) 960px, (min-width: 769px) 720px, calc(100vw - 32px)"

Note a ordem: do maior breakpoint para o menor, terminando com o fallback.

3) Defina um conjunto de larguras de arquivo (srcset com w)

Escolha larguras que cubram bem os tamanhos de exibição multiplicados pelos DPRs mais comuns. Em geral, faz sentido ter uma progressão que não seja “densa demais” (muitos arquivos) nem “rala demais” (saltos grandes). Um conjunto típico: 320, 480, 640, 800, 960, 1200, 1600, 2000 (ajuste conforme seu conteúdo).

srcset="/img/artigo-320.jpg 320w, /img/artigo-480.jpg 480w, /img/artigo-640.jpg 640w, /img/artigo-800.jpg 800w, /img/artigo-960.jpg 960w, /img/artigo-1200.jpg 1200w, /img/artigo-1600.jpg 1600w"

4) Garanta que width/height correspondam ao aspecto da imagem

Mesmo usando imagens responsivas, é importante declarar width e height no <img> para o navegador reservar espaço com a proporção correta. Isso evita instabilidade visual quando a imagem carrega. Use os valores do arquivo “base” (por exemplo, a maior versão) desde que mantenham a mesma proporção.

<img src="/img/artigo-800.jpg" alt="Ilustração do layout" width="1600" height="900" srcset="..." sizes="..." />

5) Valide a escolha do recurso em diferentes viewports e DPR

Depois de implementar, valide se o navegador está escolhendo candidatos coerentes. O objetivo é que, em cada breakpoint, o arquivo baixado esteja próximo do necessário (largura de exibição × DPR), sem exageros.

Uma validação prática é abrir a página em:

  • Mobile DPR 2 (muito comum): ver se não está baixando a versão 1600w para uma imagem exibida a ~360px de largura.
  • Desktop DPR 1: ver se não está baixando 2000w para uma imagem exibida a 960px.
  • Desktop DPR 2 (MacBook, por exemplo): ver se existe candidato suficiente para manter nitidez sem pular para um arquivo muito maior do que o necessário.

Decisões por viewport em grids e cards: o caso mais comum

Em listas de produtos, artigos ou galerias, as imagens geralmente mudam de tamanho conforme o número de colunas. Aqui, sizes precisa refletir a largura aproximada de cada card.

Exemplo: grid com 1 coluna no mobile, 2 no tablet e 4 no desktop

Suponha:

  • Mobile: card ocupa 100% da largura útil
  • Tablet (≥ 768px): 2 colunas, com gap total que equivale a ~24px
  • Desktop (≥ 1200px): 4 colunas, com container máximo de 1200px
<img alt="Produto" width="600" height="600" src="/img/p-600.jpg" srcset="/img/p-200.jpg 200w, /img/p-300.jpg 300w, /img/p-400.jpg 400w, /img/p-600.jpg 600w, /img/p-800.jpg 800w, /img/p-1000.jpg 1000w" sizes="(min-width: 1200px) 300px, (min-width: 768px) calc((100vw - 24px) / 2), calc(100vw - 32px)" />

Interpretação:

  • Acima de 1200px, cada card tem ~300px (1200/4), então declare 300px.
  • No tablet, cada card é metade do viewport menos o gap.
  • No mobile, ocupa a largura útil (descontando padding).

Esse tipo de sizes é uma aproximação. O navegador não precisa de precisão absoluta, mas quanto mais próximo do layout real, melhor a escolha do candidato.

Como escolher o “conjunto ideal” de larguras no srcset

Um srcset com muitas variações aumenta complexidade de build e pode aumentar o número de arquivos no cache/CDN. Um srcset com poucas variações pode forçar o navegador a baixar imagens maiores do que o necessário. Um equilíbrio prático:

  • Comece pelo menor tamanho realmente usado (por exemplo, 200w para thumbs pequenas).
  • Cubra os tamanhos de exibição mais comuns (larguras típicas de cards e heros).
  • Inclua candidatos para DPR 2 nos tamanhos críticos (por exemplo, se o card é 300px, tenha algo perto de 600w).
  • Evite saltos enormes (por exemplo, pular de 800w para 1600w) se isso for comum no seu layout.

Uma forma prática de decidir é listar os tamanhos de exibição (em CSS px) e multiplicar por 1 e por 2. Depois, arredonde para larguras padronizadas (320, 480, 640, 800, 960, 1200, 1600...).

Integração com CSS: object-fit, aspect-ratio e consistência de recorte

Em cards, é comum usar imagens com recorte consistente (quadrado, 4:3, 16:9). Se você usa object-fit: cover para preencher uma caixa fixa, o tamanho de exibição ainda é a largura da caixa, então sizes continua válido. Mas você deve garantir que as variações do srcset mantenham a mesma proporção (ou que o recorte seja feito no servidor) para evitar resultados inesperados.

Exemplo: card quadrado com aspect-ratio

<style> .thumb { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block; } </style> <img class="thumb" alt="Produto" width="800" height="800" src="/img/p-400.jpg" srcset="/img/p-200.jpg 200w, /img/p-300.jpg 300w, /img/p-400.jpg 400w, /img/p-600.jpg 600w, /img/p-800.jpg 800w" sizes="(min-width: 1200px) 250px, (min-width: 768px) 33vw, 50vw" />

Aqui, width/height reforçam a proporção 1:1, e o CSS garante o recorte consistente.

Checklist de implementação rápida (sem repetir temas já cobertos)

  • Use srcset com descritores w para imagens cujo tamanho varia com o viewport.
  • Escreva sizes para descrever o tamanho de exibição real do elemento em cada breakpoint (use calc() quando houver padding/gutters).
  • Use 1x/2x apenas quando o tamanho de exibição for fixo.
  • Use <picture> quando precisar mudar recorte/composição por viewport (direção de arte).
  • Mantenha um conjunto de larguras coerente e suficiente para cobrir DPR 1 e 2 nos tamanhos mais comuns.
  • Declare width e height no <img> para preservar proporção e estabilidade do layout.

Agora responda o exercício sobre o conteúdo:

Em um layout fluido em que a imagem muda de largura conforme o viewport, qual abordagem descreve corretamente como o navegador escolhe o arquivo ideal para baixar?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Quando o tamanho de exibição varia com o viewport, srcset com w precisa de sizes para estimar a largura no layout. O navegador usa essa largura (em CSS px) e multiplica pelo DPR para escolher o arquivo mais apropriado.

Próximo capitúlo

Fontes web com foco em estabilidade e rapidez: preload, subset e fallbacks

Arrow Right Icon
Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.