Back

Converting Images to Base64 with Canvas

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() over toDataURL() 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 image src.

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:

FeaturetoDataURL()toBlob()
Return typeString (synchronous)Blob (asynchronous, callback-based)
Memory usageHigher (Base64 adds ~33% overhead)Lower (binary representation)
Best forSmall images, quick embedsUploads, 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.

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.

OpenReplay