LoginSignup
83
67

More than 3 years have passed since last update.

JavaScriptでBase64エンコード・デコード(UTF-8も)

Last updated at Posted at 2019-07-26

Base64

様々なところで使われているエンコード方式です。バイナリデータから64進数にエンコードします。URLエンコードよりもデータ効率がいいらしい。

エンコード前 URLエンコード Base64エンコード
https://qiita.com/ (18) https%3A%2F%2Fqiita.com%2F (26) aHR0cHM6Ly9xaWl0YS5jb20v (24)
こんにちは (15) %E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF (45) 44GT44KT44Gr44Gh44Gv (20)

3バイトずつ4文字に変換します。

英文

マルチバイトでない文字列はbtoaatobを使ってエンコード・デコードできます。

const text = "Hello, world!";

// エンコード
const encoded = btoa(text);
//=> "SGVsbG8sIHdvcmxkIQ=="

// デコード
const decoded = atob(encoded);
//=> "Hello, world!"

日本語など

JavaScriptの文字列はUTF-16なので、そのままではエンコードできません。
Base64はあくまでもバイト列のエンコードなので、バイト列に直してやる必要があります。

btoa("テスト");
// Uncaught DOMException: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.

なので、URLエンコード等して全部1バイト文字にするか
※この方法では、UTF-8を直接エンコードしたようなものはデコードできません

const text = "テスト";

// エンコード
const encoded = btoa(encodeURIComponent(text));
//=> "JUUzJTgzJTg2JUUzJTgyJUI5JUUzJTgzJTg4"


// デコード
const decoded = decodeURIComponent(atob(encoded));
//=> "テスト"

UTF-8などのバイト列に変換してからエンコードします。
TextEncoderでUTF-8にエンコードした結果のArrayBufferString.fromCharCodeに無理やり渡しているので、あまり良い方法ではないと思われます。
また、あまり長い文字列を渡すとエラーになるようです。1

const text = "テスト";

// エンコード
let utf8str = String.fromCharCode.apply(null, new TextEncoder().encode(text));
const encoded = btoa(utf8str);
//=> "44OG44K544OI"

// デコード
const decoded_utf8str = atob(encoded);
const decoded_array = new Uint8Array(Array.prototype.map.call(decoded_utf8str, c => c.charCodeAt()));
const decoded = new TextDecoder().decode(decoded_array);
//=> "テスト"

または、Blobを経由して変換する方法もあります。2
この場合は非同期になります。

const text = "テスト";

const encodedPromise = new Promise((resolve) => {
  const reader = new FileReader();
  reader.onload = () => {
    const offset = reader.result.indexOf(",") + 1;
    resolve(reader.result.slice(offset));
  };
  reader.readAsDataURL(new Blob([text]));
});
//=> Promise {<resolved>: "44OG44K544OI"}

const decodedPromise = encodedPromise
  .then(encoded => fetch("data:text/plain;charset=UTF-8;base64," + encoded))
  .then(response => response.text());
//=> Promise {<resolved>: "テスト"}

Base64変換関数

ご利用はご自由にどうぞ。

function base64Encode(...parts) {
  return new Promise(resolve => {
    const reader = new FileReader();
    reader.onload = () => {
      const offset = reader.result.indexOf(",") + 1;
      resolve(reader.result.slice(offset));
    };
    reader.readAsDataURL(new Blob(parts));
  });
}

function base64Decode(text, charset) {
  return fetch(`data:text/plain;charset=${charset};base64,` + text).then(response => response.text());
}

function base64DecodeAsBlob(text, type = "text/plain;charset=UTF-8") {
  return fetch(`data:${type};base64,` + text).then(response => response.blob());
} 

つかいかた

// Promise#then
base64Encode("こんにちは").then(encoded => { /* ... */ });

// await
const encoded = await base64Encode("こんにちは");

// ArrayBufferやBlobも使えます
const encodedFile = await base64Encode(inputFile.files[0]);
const decodedFile = await base64DecodeAsBlob(encodedFile, "image/jpeg");
83
67
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
83
67