Pular para o conteúdo
ImageZap

Placeholders de imagem: LQIP, BlurHash, ThumbHash e cor dominante

Um bom placeholder faz seu site parecer mais rápido sem mudar um único byte das imagens. Explicamos quais técnicas existem, quanto pesam e como implementá-las bem.

Por 4 min de leitura

O problema: espaços vazios e conteúdo que pula

Quando um navegador pinta uma página, o texto aparece quase na hora, mas cada imagem precisa do seu próprio download. Enquanto isso, o usuário vê um destes dois cenários:

  1. Um espaço vazio (branco ou cinza) que de repente é preenchido pela foto. Funciona, mas parece brusco e "lento".
  2. Nada, e quando a imagem chega, ela empurra para baixo todo o conteúdo que estava embaixo. É o pior caso: além de feio, penaliza o CLS (Cumulative Layout Shift), uma das métricas dos Core Web Vitals.

O segundo cenário sempre se resolve declarando width e height (ou aspect-ratio no CSS), e é a primeira coisa que você deve fazer. O primeiro melhora com um placeholder: preencher o espaço reservado com uma aproximação da imagem que ocupe muito pouco.

As técnicas, da mais leve à mais fiel

Cor7 BGradiente≈60 BLQIP100-300 BBlurHash≈25 car.FinalAVIF
Quanto mais fiel o placeholder, mais bytes ele custa. A imagem final substitui qualquer um deles ao carregar.

1. Cor dominante (7 bytes)

Uma única cor extraída da imagem e aplicada como fundo do contêiner:

<img src="/foto.avif" width="1200" height="800" alt="…" style="background:#4a6b8a">

É o que fazem o Pinterest, o Google Imagens e o Unsplash em suas grades. Não precisa de JavaScript, não adiciona requisições e pode ser gerada no banco de dados ao enviar a imagem. Para listagens com muitas miniaturas, é difícil de superar.

2. Gradiente CSS (≈60 bytes)

Duas ou mais cores amostradas por regiões e combinadas com linear-gradient. Transmite melhor a composição ("céu em cima, areia embaixo") que uma cor sólida e continua sendo CSS puro.

3. LQIP em base64 (100-300 bytes)

Low Quality Image Placeholder: a própria imagem reduzida a 10-20 px, codificada como data URI e ampliada com um desfoque CSS. É a técnica do Medium e a que o Next.js usa com placeholder="blur". Ela parece claramente "a mesma foto desfocada", o que dá uma transição muito natural.

<img src="/foto.avif" width="1200" height="800" alt="…"
  style="background: url('data:image/webp;base64,UklGR…') center/cover">

4. BlurHash (≈20-30 caracteres)

Desenvolvido pela Wolt, codifica a imagem como alguns poucos componentes de frequência (uma transformada discreta do cosseno muito reduzida) em uma string de texto compacta. Precisa de JavaScript para ser decodificado em um canvas, mas em troca é curto o bastante para ser guardado em qualquer coluna de banco de dados e trafegar em uma API sem ser notado. Tem implementações para web, iOS, Android, Flutter, Python, Go…

5. ThumbHash (≈30-45 caracteres)

Criado por Evan Wallace em 2023 como evolução do BlurHash. Com tamanho parecido, consegue cores mais fiéis, guarda a proporção e suporta transparência, o que o torna ideal para produtos recortados ou logotipos.

Comparação rápida

TécnicaTamanhoPrecisa de JSTransparênciaFidelidade
Cor dominante7 BNãoNãoBaixa
Gradiente CSS~60 BNãoNãoBaixa-média
LQIP base64100-300 BNãoSim (PNG/WebP)Alta
BlurHash~28 BSimNãoMédia
ThumbHash~35 BSim (ou data URL pré-calculada)SimMédia-alta

Implementação no Next.js

Com imagens importadas estaticamente, o Next.js calcula o LQIP durante o build e basta uma propriedade:

import Image from "next/image";
import hero from "@/public/hero.jpg";

<Image src={hero} alt="…" placeholder="blur" priority />

Com imagens remotas (CMS, S3, Cloudinary), o Next.js não consegue baixá-las no build, então você precisa fornecer o blurDataURL. O correto é gerá-lo uma vez, quando a imagem é enviada, e guardá-lo junto com a URL:

<Image
  src={post.image.url}
  width={post.image.width}
  height={post.image.height}
  alt={post.image.alt}
  placeholder="blur"
  blurDataURL={post.image.blurDataURL}
/>

Se você não tem um processo automático, o gerador de placeholders entrega o blurDataURL de um lote de imagens em JSON pronto para importar.

Implementação com BlurHash no React

import { Blurhash } from "react-blurhash";
import { useState } from "react";

function Foto({ src, hash, width, height, alt }) {
  const [loaded, setLoaded] = useState(false);
  return (
    <div style={{ position: "relative", aspectRatio: `${width} / ${height}` }}>
      {!loaded && <Blurhash hash={hash} width="100%" height="100%" style={{ position: "absolute", inset: 0 }} />}
      <img src={src} alt={alt} width={width} height={height} loading="lazy"
        onLoad={() => setLoaded(true)} style={{ width: "100%", height: "auto" }} />
    </div>
  );
}

Repare que o contêiner tem aspect-ratio: o BlurHash não conhece a proporção da imagem e, sem ela, o placeholder não reservaria o espaço correto.

Erros que pioram o desempenho

  • Placeholders grandes demais. Um LQIP de 64 px pode passar de 2 KB. Multiplicado por 40 miniaturas, são 80 KB de HTML que o navegador precisa baixar e analisar antes de pintar. Fique em 16-20 px.
  • Decodificar BlurHash na thread principal para centenas de imagens. Cada decodificação é barata, mas centenas ao mesmo tempo bloqueiam a interação (INP). Decodifique em 32 × 32 px e escale com CSS, nunca no tamanho final.
  • Atrasar a imagem LCP. Um placeholder não substitui carregar cedo a imagem principal. A imagem LCP deve ter fetchpriority="high" e nunca loading="lazy"; o placeholder é um complemento, não uma desculpa.
  • Animações de "fade-in" longas. Uma transição de 1 segundo faz a imagem demorar 1 segundo a mais para ficar nítida. 200-300 ms são suficientes.
  • Esquecer o modo escuro. Um placeholder cinza-claro se destaca muito em um tema escuro. A cor dominante ou o LQIP se adaptam sozinhos à imagem, não ao tema.

Qual técnica escolher

  • Blog ou site de conteúdo com Next.js: placeholder="blur" para imagens locais e blurDataURL guardado no CMS para as remotas.
  • Loja virtual com grades de produto: cor dominante nas listagens e LQIP na página de produto.
  • App móvel e site que compartilham API: BlurHash ou ThumbHash no JSON.
  • Imagens com transparência: ThumbHash ou LQIP em PNG/WebP.

Perguntas frequentes

Os placeholders melhoram o LCP?

Não diretamente: o LCP é medido quando a imagem final é pintada, não o placeholder. O que eles melhoram é a percepção de velocidade e, se vierem com dimensões, evitam CLS.

O Google indexa o placeholder em vez da imagem?

Não. O Google indexa a URL do atributo src ou srcset. O placeholder vai como fundo CSS ou em um canvas, e não compete com a imagem real.

Onde guardo os placeholders?

Junto com os metadados da imagem: no seu banco de dados, no frontmatter dos seus Markdown ou no CMS. Gerá-los a cada requisição desperdiça recursos.

Guias relacionados