Como comprimir uma imagem Base64 (por que Gzip costuma falhar)
Aprenda a reduzir Base64 otimizando a imagem decodificada, por que Gzip não substitui Data URL e como buscar 100 KB com JavaScript.
Por que Base64 fica cerca de 33,3% maior
Base64 codifica três bytes em quatro caracteres. O comprimento exato é 4 × ceil(n / 3); o prefixo Data URL vem além disso.
Três fluxos possíveis
Bytes→Base64 é padrão, mas maior. Base64→Gzip→Base64 exige desfazer duas camadas. Decodificar imagem→otimizar→Base64 mantém uma imagem comum para img src.
Por que Gzip perde ganho ao voltar a texto
O Gzip intermediário pode parecer menor, mas outro Base64 e cabeçalhos adicionam custo. O resultado é Gzip, não JPEG, PNG ou WebP, e não abre diretamente.
Formatos de imagem já são comprimidos
JPEG, PNG e WebP usam compressão própria. A economia real costuma vir de menos pixels, qualidade com perda, metadados removidos ou formato adequado.
JavaScript correto no navegador
Valide Base64 e magic bytes, decodifique com ImageBitmap, desenhe em Canvas limitado e use toBlob. Em produção, limite tamanho, rejeite animação, detecte WebP e descarte tarefas antigas.
Exemplo curto com APIs nativas
async function compressBase64Image(dataUrl, maxWidth = 1600, quality = 0.82) {
const [header, payload] = dataUrl.split(',', 2);
const mime = header.match(/^data:([^;]+);base64$/)?.[1];
if (!mime || !/^image\/(png|jpeg|webp)$/.test(mime)) throw new Error('Unsupported image');
const binary = atob(payload.replace(/\s+/g, ''));
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
const bitmap = await createImageBitmap(new Blob([bytes], { type: mime }));
try {
const scale = Math.min(1, maxWidth / bitmap.width);
const canvas = document.createElement('canvas');
canvas.width = Math.max(1, Math.round(bitmap.width * scale));
canvas.height = Math.max(1, Math.round(bitmap.height * scale));
const context = canvas.getContext('2d');
if (!context) throw new Error('Canvas 2D unavailable');
context.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/webp', quality));
if (!blob) throw new Error('Encoding failed');
const output = new Uint8Array(await blob.arrayBuffer());
let outputBinary = '';
for (let offset = 0; offset < output.length; offset += 0x8000) {
outputBinary += String.fromCharCode(...output.subarray(offset, offset + 0x8000));
}
return `data:${blob.type};base64,${btoa(outputBinary)}`;
} finally {
bitmap.close();
}
}
Como chegar a 100 KB
Trate 100 KB como 102.400 bytes. Busque qualidade JPEG/WebP com limite; se necessário, redimensione com sqrt(alvo/atual) em poucas rodadas.
Qualidade, dimensões e formato
Qualidade altera o codec; redimensionar remove pixels. Fotos combinam com JPEG/WebP e transparência com PNG/WebP. Compare a saída real.
Transparência PNG e fundo JPEG
JPEG não tem alfa. Mantenha PNG/WebP ou exija fundo confirmado antes de achatar.
GIF, WebP animado e SVG
Canvas normalmente captura um quadro. Recuse GIF, WebP animado e ICO; SVG precisa de otimização textual própria.
Local melhora privacidade, não prova segurança
Evita upload e retenção, mas não substitui antivírus. Dispositivo, extensões e codecs continuam relevantes.
Quando abandonar Base64
Para imagens grandes, use Blob URL, arquivo separado com cache ou upload binário via Blob, FormData ou ArrayBuffer.
Perguntas frequentes
Base64 pode ser comprimido?
Sim, mas compressão genérica não gera Base64 de imagem diretamente utilizável.
Funciona em img src?
Só se o conteúdo decodificado continuar sendo imagem; Gzip precisa ser aberto.
Como chegar a 100 KB?
Busque qualidade com limite e depois reduza dimensões proporcionalmente.
Perde qualidade?
Redimensionar e usar qualidade com perda pode reduzir a qualidade.
Por que 33% maior?
Três bytes são representados por quatro caracteres.
É enviado ao servidor?
O compressor trabalha somente no navegador.
Gzip antes ou depois?
Use compressão HTTP ao redor do documento, não dentro da Data URL.