Преобразование изображений в Base64 с помощью Canvas
Когда вам нужно экспортировать данные изображения из браузера — будь то для клиентских превью, встраивания небольшой графики или обработки изображений перед загрузкой — HTML Canvas API является наиболее прямым доступным инструментом. В этой статье объясняется, как работает преобразование canvas в Base64 на JavaScript, когда его использовать и где находятся распространённые подводные камни.
Ключевые моменты
- Метод canvas
toDataURL()возвращает полный data URI (с MIME-префиксом), а не чистую строку Base64. Удаляйте префикс, когда вам нужны только закодированные данные. - Предпочитайте
toBlob()вместоtoDataURL()для загрузки файлов и больших изображений, так как он асинхронный и более эффективен по памяти. - PNG — единственный универсально поддерживаемый выходной формат. Поддержка JPEG и WebP варьируется в зависимости от браузера.
- Изображения с другого источника (cross-origin) «загрязнят» canvas, если сервер не отправляет правильные CORS-заголовки и вы не установите
crossOrigin = 'anonymous'перед присвоениемsrcизображению.
Базовый рабочий процесс
Преобразование изображения в Base64 с помощью canvas следует трём шагам: загрузить изображение, нарисовать его на элементе canvas, затем экспортировать данные canvas в виде закодированной строки.
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const image = new Image();
image.onload = function () {
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0);
const dataURL = canvas.toDataURL('image/png');
console.log(dataURL); // "data:image/png;base64,iVBORw0KGg..."
};
image.src = '/path/to/local-image.jpg';
Это основа кодирования изображений на базе canvas. Метод toDataURL() возвращает полный data URI, а не чистую строку Base64.
Data URL против чистой строки Base64
Понимание разницы имеет значение. canvas.toDataURL() возвращает строку в таком формате:
data:image/png;base64,iVBORw0KGg...
Она включает префикс MIME-типа, метку кодирования, а затем собственно данные Base64. Если вам нужна только чистая часть Base64 — например, для отправки в JSON на сервер — удалите префикс:
const base64String = canvas.toDataURL('image/jpeg').split(',')[1];
Это разделяет строку по первой запятой и берёт всё после неё. Префикс отбрасывается, и остаются чистые закодированные данные.
toDataURL против toBlob: что использовать?
Это решение, которое большинство статей обходит стороной. Вот прямое сравнение:
| Характеристика | toDataURL() | toBlob() |
|---|---|---|
| Тип возвращаемого значения | String (синхронный) | Blob (асинхронный, на основе callback) |
| Использование памяти | Выше (Base64 добавляет ~33% накладных расходов) | Ниже (бинарное представление) |
| Лучше всего для | Небольших изображений, быстрых встраиваний | Загрузок, больших изображений |
toDataURL() синхронный и удобный, но он загружает всю закодированную строку в память сразу. Кодирование Base64 увеличивает размер данных примерно на 33% по сравнению с исходным бинарным форматом. Для больших изображений это имеет значение.
toBlob() асинхронный и создаёт бинарный Blob напрямую, что более эффективно для загрузок:
canvas.toBlob((blob) => {
const formData = new FormData();
formData.append('image', blob, 'export.jpg');
fetch('/upload', { method: 'POST', body: formData });
}, 'image/jpeg', 0.85);
Используйте Base64 только когда вам конкретно нужна строка — для встраивания в JSON, хранения в текстовом поле или генерации data URI для тега <img>. Для загрузки файлов toBlob() — лучший выбор.
Discover how at OpenReplay.com.
Поддержка форматов и параметр качества
PNG — единственный выходной формат, гарантированно поддерживаемый во всех браузерах. Поддержка JPEG и WebP зависит от окружения:
canvas.toDataURL('image/jpeg', 0.85); // качество: от 0 до 1, только для JPEG/WebP
canvas.toDataURL('image/webp', 0.9); // не поддерживается во всех браузерах
Параметр качества не влияет на PNG, который всегда сжимается без потерь. Для JPEG 0.85 — разумное значение по умолчанию, балансирующее размер файла и визуальное качество. Если вы передаёте неподдерживаемый формат, браузер молча откатывается к PNG.
Проблема «загрязнённого» canvas
Если вы рисуете изображение с другого источника (cross-origin) на canvas без правильной конфигурации CORS, canvas становится «загрязнённым» (tainted). Вызов toDataURL() или toBlob() на загрязнённом canvas выбрасывает SecurityError.
Чтобы избежать этого, сервер, хостящий изображение, должен отправлять соответствующие CORS-заголовки (Access-Control-Allow-Origin), и вы должны установить crossOrigin на элементе изображения до присвоения его src:
const image = new Image();
image.crossOrigin = 'anonymous';
image.src = 'https://other-origin.com/image.png';
Порядок здесь критичен. Установка crossOrigin после src может привести к тому, что браузер начнёт загрузку без CORS-флага, что всё равно загрязнит canvas. Изображения, загруженные с того же источника, никогда не создают проблем. Изображения с другого источника без CORS-заголовков не могут быть экспортированы — клиентского обходного пути не существует.
Когда кодирование на основе Canvas имеет смысл
Используйте подход с canvas, когда вам нужно:
- Изменить размер или обрезать изображение на стороне клиента перед кодированием
- Применить фильтры или преобразования перед экспортом
- Встроить небольшие обработанные изображения как data URI
Если вам нужно только прочитать локальный файл как Base64 без какой-либо манипуляции с canvas, FileReader.readAsDataURL() проще и полностью пропускает canvas.
Заключение
Canvas API даёт вам точный контроль над кодированием изображений в браузере. Используйте toDataURL() для небольших изображений и случаев использования на основе строк, предпочитайте toBlob() для загрузок и работы, критичной к производительности, и всегда учитывайте CORS при работе с внешними изображениями. Когда манипуляция пикселями не требуется, пропустите canvas полностью и используйте FileReader.
Часто задаваемые вопросы
Да, вы можете нарисовать SVG на canvas и вызвать toDataURL(). Однако SVG должен быть сначала загружен как элемент Image, и ограничения cross-origin всё ещё применяются. Экспортированный результат — это растеризованное bitmap-изображение, а не масштабируемый вектор. Любые внешние ресурсы, на которые ссылается SVG, такие как шрифты или связанные изображения, могут некорректно отрендериться на canvas.
Обычно это происходит потому, что toDataURL() вызывается до того, как изображение закончит загрузку. Убедитесь, что вы вызываете его внутри обработчика onload изображения. Чёрное изображение также может быть результатом того, что вы не установили ширину и высоту canvas в соответствии с размерами исходного изображения перед рисованием, что оставляет canvas с размером по умолчанию 300 на 150 пикселей.
Формального ограничения в спецификации нет, но браузеры накладывают практические ограничения. Очень большие canvas могут создавать строки Base64, которые потребляют значительную память или приводят к замедлению или краху вкладки браузера. Для больших изображений toBlob() — более безопасная и эффективная по памяти альтернатива.
Стандартные элементы canvas недоступны в Web Workers, потому что они зависят от DOM. Однако вы можете использовать OffscreenCanvas, который поддерживается в современных браузерах, для выполнения рисования и вызова его метода convertToBlob() внутри worker. Обратите внимание, что OffscreenCanvas не поддерживает toDataURL(), поэтому вам нужно будет преобразовать полученный Blob в Base64 с помощью FileReader, если требуется строка.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.