Pular para o conteúdo
← Voltar para o blog
Engenharia de Frontend

Como exportar um criativo Astro responsivo em PNG com proporção personalizada

9 min de leitura

Escrito por: Jonathan Reis em

Uma página de marketing pode virar imagem compartilhável sem depender de um preset social fixo. Quem prepara o asset escolhe a proporção, revisa a área de captura e exporta a superfície renderizada.

Atualizado:

Imagem de prévia OpenGraph deste artigo. Como exportar um criativo Astro responsivo em PNG com proporção personalizada

Uma página estática de marketing costuma ser o lugar mais rápido para compor um criativo de campanha. Ela já tem os screenshots, a tipografia, o espaçamento, a tradução e a copy que precisam ficar sincronizados. O problema prático vem depois:

É tentador colocar um botão que sempre exporta 1080 × 1350. Funciona para um destino, depois vira limitação. Um card de comunidade pode pedir uma imagem larga, enquanto outra superfície pede um asset vertical. Alterar o CSS à mão, fazer outro screenshot e torcer para o arquivo ter a proporção certa não é uma etapa confiável de produção.

O contrato mais útil é direto: a área de captura tem uma proporção editável, a página mostra esse limite e o PNG baixado usa as mesma superfície sem uma segunda etapa de redimensionamento.

Faça da proporção parte do fluxo de edição

O alvo da exportação deve ser apenas o criativo, não o documento inteiro. Deixe controles de proporção, botão de exportação e status fora dele para que nunca apareçam no arquivo baixado. Uma moldura visível em volta do alvo deixa o limite claro durante a edição; a moldura também fica fora do alvo.

O campo aceita uma proporção decimal ou uma expressão de largura por altura. Normalize vírgula decimal, natural para quem digita 1,57, e aplique o resultado como propriedade CSS personalizada na moldura:

function parseAspectRatio(value: string) {
  const normalizedValue = value.trim().replace(",", ".");
  const parts = normalizedValue.split(":").map(Number);
  const ratio = parts.length === 2 ? parts[0] / parts[1] : Number(normalizedValue);

  return Number.isFinite(ratio) && ratio > 0 ? ratio : null;
}

function applyAspectRatio(frame: HTMLElement, value: string) {
  const ratio = parseAspectRatio(value) ?? 1.57;
  frame.style.setProperty("--marketing-aspect-ratio", String(ratio));
}

O alvo usa essa variável por aspect-ratio: var(--marketing-aspect-ratio). A largura continua responsiva ao espaço disponível; marque o campo como inválido para que a pessoa possa corrigir o valor.

O fluxo de preparação fica explícito:

  1. Informe a proporção necessária.
  2. Revise a composição dentro da moldura de captura.
  3. Leia as dimensões PNG renderizadas.
  4. Exporte.

Não existe corte escondido depois da revisão. O arquivo é a mesma composição que foi inspecionada.

Mostre o tamanho antes do download

Mostrar a dimensão depois de baixar é tarde demais. Quem escolhe o tamanho precisa dessa informação enquanto ajusta o viewport, não depois de gerar um arquivo que talvez descarte.

Um ResizeObserver mantém o rótulo ligado ao alvo, em vez de depender de uma lista fixa de presets sociais. Meça a caixa real imediatamente antes da exportação e sempre que ela mudar:

function getExportDimensions(target: HTMLElement) {
  const bounds = target.getBoundingClientRect();
  return {
    width: Math.round(bounds.width),
    height: Math.round(bounds.height),
  };
}

const updateDimensions = () => {
  const { width, height } = getExportDimensions(target);
  dimensionsLabel.textContent = `${width} × ${height}px`;
};

updateDimensions();
new ResizeObserver(updateDimensions).observe(target);

Para um criativo delimitado por moldura, getBoundingClientRect().height é mais preciso do que scrollHeight: a caixa com aspect-ratio é o limite intencional do arquivo. scrollHeight mede conteúdo que ultrapassa a caixa, útil num documento longo com rolagem, mas capaz de produzir uma altura inesperada num card fixo. O rótulo torna essa decisão explícita antes de o arquivo chegar à pasta Downloads.

Rasterize o alvo renderizado, não uma cópia fora da tela

Usamos html-to-image para converter o elemento em uma data URL PNG. A chamada principal é curta:

const { width, height } = getExportDimensions(target);
const dataUrl = await toPng(target, {
  backgroundColor: "#f6f2ea",
  canvasWidth: width,
  canvasHeight: height,
  width,
  height,
  pixelRatio: 1,
  cacheBust: true,
});

O entorno dessa chamada importa mais que ela.

A primeira versão clonava a página, forçava uma largura canônica e colocava a cópia bem fora do viewport. Parecia uma boa ideia porque deixava o arquivo independente da página visível. No Chrome, ela produziu PNGs em branco. O rasterizador usa o caminho do navegador baseado em foreignObject; um nó colocado milhares de pixels fora da área renderizável não é uma fonte de captura confiável.

Havia outro problema menos óbvio. Clonar um elemento cria novas imagens. Mesmo que os screenshots originais estivessem visíveis, as imagens da cópia podiam ainda estar carregando quando a exportação começava. O arquivo tinha o tamanho correto, mas faltava conteúdo.

Capturar o alvo que já está renderizado remove as duas incertezas. Também faz o resultado corresponder à superfície de captura visível, que é o objetivo desse fluxo.

Espere fontes e screenshots

Não considere que uma página visível está pronta para rasterizar. Uma troca de fonte pode mudar as quebras de linha, e uma imagem pode terminar de decodificar depois de o resto do layout aparecer. Espere pelos dois antes de chamar toPng:

await document.fonts.ready;

await Promise.all(
  [...target.querySelectorAll("img")].map(async (image) => {
    if (!image.complete) {
      await new Promise<void>((resolve, reject) => {
        image.addEventListener("load", () => resolve(), { once: true });
        image.addEventListener("error", () => reject(new Error("Image failed to load")), {
          once: true,
        });
      });
    }

    await image.decode();
  }),
);

image.complete só informa que o carregamento terminou. Não garante que a imagem está pronta para pintura. decode() fecha essa lacuna. Se um screenshot não carregar, rejeite a exportação e mostre uma mensagem para tentar de novo, em vez de baixar um criativo quebrado que parece ter dado certo.

Mantenha os controles fora da imagem

O alvo deve conter apenas a arte. Deixe botão, status, ferramentas de debug e instruções de modo responsivo ao lado dele ou num overlay fixo fora da raiz de captura. Assim o PNG fica limpo e a página pode continuar oferecendo controles úteis sem precisar escondê-los com CSS durante a exportação.

Desabilite o botão enquanto a conversão ocorre. Rasterizar uma página alta é assíncrono; um segundo clique pode baixar um arquivo duplicado ou disputar recursos temporários do navegador. Restaure o texto original em finally, tanto no sucesso quanto no erro.

Verifique pixels, não apenas dimensões

Um arquivo baixado com largura e altura esperadas não prova que a exportação funcionou. Encontramos uma imagem branca com as dimensões corretas porque o canvas foi criado, mas o conteúdo não foi pintado.

O loop de validação é curto:

  • Exporte uma proporção larga, como 1,57, e uma vertical, como 4:5.
  • Abra os dois arquivos e confira screenshots, texto e rodapé final.
  • Confirme que a dimensão exibida antes da exportação bate com o arquivo baixado.
  • Confira que cada proporção é recomposta, em vez de ser só uma versão reduzida de outro card.

Num lançamento em comunidade, o criativo faz parte da explicação do produto. O guia sobre como divulgar um app no Reddit com engajamento orgânico e medição honesta cobre a outra parte: escolher uma comunidade que aceita o formato, usar mídia real e pedir feedback específico. Um botão de PNG responsivo não torna o post bom. Ele remove um atrito evitável na preparação.

Checklist de exportação

  • Separe o alvo de exportação dos controles.
  • Faça o parse e valide uma proporção decimal ou largura:altura.
  • Faça do limite visível do alvo a fonte das dimensões PNG.
  • Atualize a dimensão com ResizeObserver.
  • Espere fontes e execute decode() nos screenshots.
  • Valide os pixels, não apenas os metadados do PNG.

A decisão central é fazer da superfície de captura a fonte de verdade. Se a pessoa escolheu 4:5, a moldura, o rótulo de tamanho e o PNG baixado precisam descrever o mesmo card. Assim, a exportação no navegador deixa de ser um segundo sistema de

Post relacionadoComo divulgar um app no Reddit com engajamento orgânico e medição honesta10 min de leituraEscrito por: Jonathan Reis em