Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
47
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

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

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");
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
47
Help us understand the problem. What are the problem?