Как сжать изображение Base64 (почему Gzip обычно не помогает)
Узнайте, как уменьшать Base64 через оптимизацию изображения, почему Gzip не заменяет Data URL и как стремиться к 100 КБ в JavaScript.
Почему Base64 больше примерно на 33,3%
Base64 кодирует три байта четырьмя символами. Точная длина — 4 × ceil(n / 3), без префикса Data URL.
Три цепочки обработки
Байты→Base64 стандартны, но больше. Base64→Gzip→Base64 требует снять два слоя. Декодировать изображение→оптимизировать→Base64 сохраняет обычное изображение для img src.
Почему выигрыш Gzip исчезает после возврата к тексту
Промежуточный Gzip может быть меньше, но новый Base64 и заголовки добавляют объём. Результат — Gzip, а не JPEG, PNG или WebP, и напрямую не отображается.
Форматы изображений уже сжаты
JPEG, PNG и WebP применяют своё сжатие. Реальная экономия обычно даётся меньшим числом пикселей, качеством с потерями, удалением метаданных или сменой формата.
Правильный JavaScript в браузере
Проверьте Base64 и magic bytes, декодируйте ImageBitmap, рисуйте на ограниченной Canvas и используйте toBlob. В рабочем коде нужны лимиты, запрет анимации, проверка WebP и защита от старых задач.
Короткий пример на встроенных API
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();
}
}
Как получить 100 КБ
Считайте 100 КБ как 102 400 байт. Ограниченно ищите качество JPEG/WebP; затем масштабируйте с sqrt(цель/текущее) всего несколько раундов.
Качество, размеры и формат
Качество меняет codec, масштабирование удаляет пиксели. Фото часто подходят JPEG/WebP, прозрачность — PNG/WebP. Сравнивайте реальные файлы.
Прозрачность PNG и фон JPEG
У JPEG нет alpha. Сохраняйте PNG/WebP или требуйте подтверждённый фон перед сведением.
GIF, анимированный WebP и SVG
Canvas обычно берёт один кадр. Отклоняйте GIF, WebP-анимацию и ICO; для SVG нужен отдельный текстовый оптимизатор.
Локально — приватнее, но не гарантированно безопасно
Нет загрузки и серверного хранения, но это не антивирус. Риски устройства, расширений и browser codec остаются.
Когда отказаться от Base64
Для больших изображений лучше Blob URL, отдельный кэшируемый файл или бинарная отправка Blob/FormData/ArrayBuffer.
Частые вопросы
Можно сжать Base64?
Да, но общее сжатие не даёт напрямую используемый Base64 изображения.
Работает в img src?
Только если после декодирования это изображение; Gzip надо распаковать.
Как получить 100 КБ?
Ограниченно ищите качество, затем пропорционально уменьшайте размеры.
Качество снизится?
Масштабирование и lossy-качество могут его снизить.
Почему +33%?
Три байта представлены четырьмя символами.
Есть загрузка?
Компрессор работает только в браузере.
Gzip до или после?
Используйте HTTP-сжатие вокруг документа, не внутри Data URL.