A browser can render an image straight from a string instead of a URL. A data: URI like data:image/png;base64,iVBORw0KGgo… carries the whole file inline as base64 text — paste it into background-image: url(…) or an <img src> and there is no separate network request, the bytes travel with your CSS or HTML. I built a tool that turns a dropped image into one, shows a live preview, the full byte math, and a verdict on whether inlining is a good idea. It has no server and no library, because it turns out it doesn't need one. Here is what I learned building it.
The whole engine is one browser call
The instinct is to reach for an encoder. You don't need one. FileReader.readAsDataURL(file) reads the file's bytes in the browser and hands back a finished data: URI — MIME type sniffed, base64 done — on the async onload. Nothing is uploaded, which is exactly why a converter like this needs no backend.
function handleFile(file){
if (!file || !file.type.startsWith("image/"))
return showError("That's not an image file.");
const reader = new FileReader();
reader.onload = e => render(file, e.target.result); // result = the data URI
reader.onerror = () => showError("Could not read the file.");
reader.readAsDataURL(file); // async → onload fires with the string
}
Everything after that is string slicing and one division.
Splitting the URI is one indexOf
A data URI is data:<mediatype>[;base64],<data>. Everything before the first comma is the header (scheme + MIME + encoding flag); everything after is the payload. One indexOf(",") and two slices, no parser.
function parseDataURI(uri){
const comma = uri.indexOf(",");
const header = uri.slice(0, comma); // "data:image/png;base64"
const payload = uri.slice(comma + 1); // "iVBORw0KGgoAAAANS..."
const mime = header.slice(5, header.indexOf(";")); // "image/png"
return { header, payload, mime };
}
The ~33% overhead, from first principles
This is the number the tool exists to make visible. Base64 regroups bits: it takes 3 bytes (24 bits) at a time and re-splits them into 4 groups of 6 bits, mapping each to one of 64 safe characters. So it always emits 4 characters for every 3 input bytes, and since each character is one byte of text, the encoded size is 4/3 of the original — about 33% bigger. The original size is just file.size; the base64 size is payload.length because ASCII is one byte per char.
function byteStats(file, payload){
const orig = file.size; // raw bytes
const b64 = payload.length; // 1 ASCII char = 1 byte
const theory = 4 * Math.ceil(orig / 3); // 3 bytes -> 4 chars
const overheadPct = (b64 - orig) / orig * 100;
return { orig, b64, theory, overheadPct };
}
// e.g. 900 B image -> 1200 B base64 -> +33.3%
The subtlety worth stating out loud: this is growth, not shrinkage. Base64 is an encoding, not compression. It exists so binary bytes survive in text-only places like a CSS file or a JSON field. If you want smaller, compress the image first, then encode.
When to inline — the actual judgment
The point of the tool isn't the conversion, it's the verdict. Inlining is a trade. It wins for small, single-use assets — icons, bullets, tiny SVGs under a few KB — where you save a whole network round-trip and the 33% tax is trivial in absolute terms. It loses for anything big or shared, for two reasons. A data URI has no URL of its own, so it can't be cached: an inlined logo re-downloads inside every page that embeds it, instead of being fetched once. And a large base64 blob sits in the critical path — the browser has to parse past it before it can finish the stylesheet or the HTML.
function verdict(orig){
if (orig <= 4 * 1024) return ["good", "Great inline candidate — smaller than a round-trip."];
if (orig <= 40 * 1024) return ["meh", "Borderline — inline only if it's used on one page."];
return ["bad", "Too big to inline — link it as a file so it can be cached."];
}
One last thing people get wrong: a data URI is not private. Base64 looks scrambled but it's a public, reversible encoding — anyone can copy the string, run atob(), and get the exact original file back. Encode for convenience and request-saving, never for secrecy.
The lesson underneath the whole tool: a data URI is just a file that learned to travel as text, and text is 33% heavier than the bytes it carries.
Drop an image and see the real byte cost:
https://dev48v.infy.uk/solve/day42-image-to-datauri.html
Top comments (0)