NodeはBufferにbase64エンコード機能があるのだが
Buffer.from("あいうえお", "utf-8").toString("base64")
ブラウザのJSにはこういう便利な実装がないのだった。どうするか?
window.btoa
ブラウザにはbtoaというbase64エンコードを行う関数がある。しかしこいつはascii文字列またはBinary Stringしか受け付けず、日本語を渡すと例外を投げるという英語圏仕様のクソ関数。このためbtoaを使うには一度binary stringに変換しなくてはならない。
文字列からbinary stringに変換する最も簡単な方法は、URLエンコードしてunescape
const str = "🐱"; const binStr = unescape(encodeURIComponent(str)) btoa(binStr);
FileReader#readAsBinaryStringは、FileやBlobをbinary stringに変換するメソッド。これで変換してbtoaに渡せばbase64文字列を得られる。
const blob = new Blob(["🐱"]); const fr = new FileReader() fr.readAsBinaryString(blob); fr.onload = () => { btoa(fr.result); };
String.fromCharCodeを使うとUint8Arrayからbinary stringに変換できる。文字列からUint8Arrayに変換するにはTextEncoderというオブジェクトを使う。
const str = "🐱"; const uint8arr = new TextEncoder("utf-8").encode(str); const binStr = Array.from(uint8arr).map(b => String.fromCharCode(b)).join(""); btoa(binStr);
binary stringはTyped Arrayが実装される以前のバイナリを扱うためのレガシーな形式なので、できればUint8Arrayのまま処理したい。
readAsDataURLを使う
FileReader#readAsDataURLは FileやBlobをデータURIスキームに変換するメソッドである。
data:application/octet-stream;base64,8J+QsQ==
データ部分はbase64エンコードされるので、そこだけ切り出せば良い。
const blob = new Blob(["🐱"]); const fr = new FileReader() fr.readAsDataURL(blob); fr.onload = () => { const r = fr.result; r.slice(r.indexOf(',') + 1); };
base64エンコーダーを実装する
Uint8Arrayを直接base64にできないなら自分でエンコード処理を書いてしまえ。
function encodeBase64(bytes) { const table = []; for (let i = 65; i < 91; i++) table.push(String.fromCharCode(i)) for (let i = 97; i < 123; i++) table.push(String.fromCharCode(i)) for (let i = 0; i < 10; i++) table.push(i.toString(10)) table.push("+"); table.push("/"); let base64 = ""; let i = 0; const len = bytes.byteLength; for (i = 0; i < len; i += 3) { if (len === i + 1) { // last 1 byte const a = (bytes[i] & 0xfc) >> 2; const b = ((bytes[i] & 0x03) << 4); base64 += table[a]; base64 += table[b]; base64 += "=="; } else if (len === i + 2) { // last 2 bytes const a = (bytes[i] & 0xfc) >> 2; const b = ((bytes[i] & 0x03) << 4) | ((bytes[i+1] & 0xf0) >> 4); const c = ((bytes[i+1] & 0x0f) << 2); base64 += table[a]; base64 += table[b]; base64 += table[c]; base64 += "="; } else { const a = (bytes[i] & 0xfc) >> 2; const b = ((bytes[i] & 0x03) << 4) | ((bytes[i+1] & 0xf0) >> 4); const c = ((bytes[i+1] & 0x0f) << 2) | ((bytes[i+2] & 0xc0) >> 6); const d = bytes[i+2] & 0x3f; base64 += table[a]; base64 += table[b]; base64 += table[c]; base64 += table[d]; } } return base64; } const uint8Arr = Uint8Array.of(0xF0, 0x9F, 0x90, 0xB1); encodeBase64(uint8arr); const uint8arr2 = new TextEncoder("utf-8").encode("🐱"); encodeBase64(uint8arr2);