b64tool: Encode Images to Data URLs Without Uploading to Random Sites
Needed Base64 for a static site's inline SVG fallback. Built encode/decode with file picker, UTF-8 safety, and the atob Unicode crash fix.
Scenario: Client refused third-party encoder sites for contract PDF thumbnails. Everything had to run client-side.
Full tool
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Base64 Encode / Decode</title>
<style>
body { font-family: system-ui; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
textarea { width: 100%; min-height: 120px; font-family: monospace; font-size: 12px; }
.row { margin: .75rem 0; display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; }
#preview { max-width: 200px; max-height: 200px; margin-top: 1rem; }
.err { color: #b91c1c; }
</style>
</head>
<body>
<h1>Base64 Tool</h1>
<div class="row">
<input type="file" id="file" accept="image/*,.txt,.json">
<button id="enc-text">Encode text</button>
<button id="dec-text">Decode to text</button>
<button id="copy">Copy output</button>
</div>
<textarea id="in" placeholder="Text or paste base64"></textarea>
<textarea id="out" readonly placeholder="Result"></textarea>
<p id="msg"></p>
<img id="preview" alt="" hidden>
<script>
const input = document.getElementById('in');
const output = document.getElementById('out');
const msg = document.getElementById('msg');
const preview = document.getElementById('preview');
function utf8ToB64(str) {
const bytes = new TextEncoder().encode(str);
let bin = '';
bytes.forEach(b => bin += String.fromCharCode(b));
return btoa(bin);
}
function b64ToUtf8(b64) {
const bin = atob(b64.replace(/\s/g, ''));
const bytes = Uint8Array.from(bin, c => c.charCodeAt(0));
return new TextDecoder().decode(bytes);
}
document.getElementById('enc-text').onclick = () => {
msg.textContent = '';
try {
output.value = utf8ToB64(input.value);
} catch (e) { msg.textContent = e.message; msg.className = 'err'; }
};
document.getElementById('dec-text').onclick = () => {
msg.textContent = '';
try {
output.value = b64ToUtf8(input.value);
} catch (e) { msg.textContent = 'Invalid Base64: ' + e.message; msg.className = 'err'; }
};
document.getElementById('file').onchange = async (e) => {
const file = e.target.files?.[0];
if (!file) return;
const buf = await file.arrayBuffer();
const bytes = new Uint8Array(buf);
let bin = '';
bytes.forEach(b => bin += String.fromCharCode(b));
const b64 = btoa(bin);
const mime = file.type || 'application/octet-stream';
output.value = 'data:' + mime + ';base64,' + b64;
if (mime.startsWith('image/')) {
preview.src = output.value;
preview.hidden = false;
} else {
preview.hidden = true;
}
msg.textContent = file.name + ' (' + (file.size / 1024).toFixed(1) + ' KB)';
msg.className = '';
};
document.getElementById('copy').onclick = () => {
navigator.clipboard.writeText(output.value);
msg.textContent = 'Copied.';
};
</script>
</body>
</html>
Classic Unicode crash
btoa('café') // InvalidCharacterError
Never use raw btoa on user strings. TextEncoder → binary string → btoa is the fix above.
Data URL size warning
A 400KB PNG becomes ~533KB Base64. I warn above 100KB:
if (file.size > 100_000) msg.textContent += ' — large for inline CSS';
Verify
- Encode
Hello 世界→ decode → same string - Upload PNG → data URL + preview shows
- Paste invalid
!!!base64 → clear error