Converting Images to Base64 with Canvas
When you need to export image data from the browser—whether for client-side previews, embedding small graphics, or processing images before upload—the HTML Canvas API is the most direct tool available. This article explains how JavaScript canvas-to-Base64 conversion works, when to use it, and where the common pitfalls are.
Key Takeaways
- The canvas
toDataURL()method returns a full data URI (with MIME prefix), not a raw Base64 string. Strip the prefix when you need only the encoded data. - Prefer
toBlob()overtoDataURL()for file uploads and large images, as it is asynchronous and more memory-efficient. - PNG is the only universally supported output format. JPEG and WebP support varies by browser.
- Cross-origin images will taint the canvas unless the server sends proper CORS headers and you set
crossOrigin = 'anonymous'before assigning the imagesrc.
The Basic Workflow
Converting an image to Base64 using canvas follows three steps: load the image, draw it onto a canvas element, then export the canvas data as an encoded string.
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';
This is the foundation of canvas-based image encoding. The toDataURL() method returns a complete data URI, not a raw Base64 string.
Data URL vs. Raw Base64 String
Understanding the difference matters. canvas.toDataURL() returns a string in this format:
data:image/png;base64,iVBORw0KGg...
It includes a MIME type prefix, the encoding label, and then the actual Base64 data. If you need only the raw Base64 portion—for example, to send it as JSON to a server—strip the prefix:
const base64String = canvas.toDataURL('image/jpeg').split(',')[1];
This splits on the first comma and takes everything after it. The prefix is discarded, and the raw encoded data remains.
toDataURL vs. toBlob: Which Should You Use?
This is the decision most articles skip over. Here is a direct comparison:
| Feature | toDataURL() | toBlob() |
|---|---|---|
| Return type | String (synchronous) | Blob (asynchronous, callback-based) |
| Memory usage | Higher (Base64 adds ~33% overhead) | Lower (binary representation) |
| Best for | Small images, quick embeds | Uploads, large images |
toDataURL() is synchronous and convenient, but it loads the entire encoded string into memory at once. Base64 encoding increases data size by roughly 33% compared to the original binary. For large images, this matters.
toBlob() is asynchronous and produces a binary Blob directly, which is more efficient for uploads:
canvas.toBlob((blob) => {
const formData = new FormData();
formData.append('image', blob, 'export.jpg');
fetch('/upload', { method: 'POST', body: formData });
}, 'image/jpeg', 0.85);
Use Base64 only when you specifically need a string—for embedding in JSON, storing in a text field, or generating a data URI for an <img> tag. For file uploads, toBlob() is the better choice.
Discover how at OpenReplay.com.
Format Support and the Quality Parameter
PNG is the only output format guaranteed across all browsers. JPEG and WebP support depends on the environment:
canvas.toDataURL('image/jpeg', 0.85); // quality: 0 to 1, JPEG/WebP only
canvas.toDataURL('image/webp', 0.9); // not supported in all browsers
The quality parameter has no effect on PNG, which is always lossless. For JPEG, 0.85 is a reasonable default that balances file size and visual quality. If you pass an unsupported format, the browser silently falls back to PNG.
The Tainted Canvas Problem
If you draw a cross-origin image onto a canvas without proper CORS configuration, the canvas becomes “tainted.” Calling toDataURL() or toBlob() on a tainted canvas throws a SecurityError.
To avoid this, the server hosting the image must send appropriate CORS headers (Access-Control-Allow-Origin), and you must set crossOrigin on the image element before assigning its src:
const image = new Image();
image.crossOrigin = 'anonymous';
image.src = 'https://other-origin.com/image.png';
The order here is critical. Setting crossOrigin after src can cause the browser to begin fetching without the CORS flag, which still taints the canvas. Images loaded from the same origin are never a problem. Cross-origin images without CORS headers cannot be exported—there is no client-side workaround.
When Canvas-Based Encoding Makes Sense
Use the canvas approach when you need to:
- Resize or crop an image client-side before encoding
- Apply filters or transformations before exporting
- Embed small processed images as data URIs
If you only need to read a local file as Base64 without any canvas manipulation, FileReader.readAsDataURL() is simpler and skips the canvas entirely.
Conclusion
The Canvas API gives you precise control over image encoding in the browser. Use toDataURL() for small images and string-based use cases, prefer toBlob() for uploads and performance-sensitive work, and always account for CORS when working with external images. When no pixel manipulation is required, skip the canvas altogether and reach for FileReader instead.
FAQs
Yes, you can draw an SVG onto a canvas and call toDataURL(). However, the SVG must be loaded as an Image element first, and cross-origin restrictions still apply. The exported result is a rasterized bitmap, not a scalable vector. Any external resources referenced by the SVG, such as fonts or linked images, may not render correctly on the canvas.
This usually happens because toDataURL() is called before the image finishes loading. Make sure you call it inside the image's onload handler. A black image can also result from not setting the canvas width and height to match the source image dimensions before drawing, which leaves the canvas at its default size of 300 by 150 pixels.
There is no formal specification limit, but browsers impose practical constraints. Very large canvases can produce Base64 strings that consume significant memory or cause the browser tab to slow down or crash. For large images, toBlob() is the safer and more memory-efficient alternative.
Standard canvas elements are not available in Web Workers because they depend on the DOM. However, you can use OffscreenCanvas, which is supported in modern browsers, to perform drawing and call its convertToBlob() method inside a worker. Note that OffscreenCanvas does not support toDataURL(), so you would need to convert the resulting Blob to Base64 using a FileReader if a string is required.
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.