Base64 形式を扱う必要がありますか?それならこのサイトが最適です!データをエンコードまたはデコードするために便利なオンラインツールをご利用ください。

JavaScript/Browser での Base64 エンコード:完全ガイド

届けなければいけないものがあるとして、道はプレーンな ASCII にしか広くなっていない。JSON レスポンスの中に属する画像、URL に同乗させなければならない設定オブジェクト、3 つのセグメントがドットと文字でできているトークン、API が JSON 本体の中を Base64 文字列で届くことを要求するファイル、かもしれません。Base64 は、まさにこの状況の料金所です。このサイトのホームページがこの形式をすでに段階を追って説明しているので - 表示できる 4 文字がバイト 3 バイトに代わり、グループの仕上げに = パディング - 読むあいだ頭に入れておくべき 1 つの数字があります:エンコードは増える方向です。渡すバイト 3 バイトすべてが 4 文字になって返ってくる。約 33% のサイズ税で、帯域、ストレージ、メモリで徴収されます。チャネルが表示できるテキストを要求するときに Base64 を使い、その税がいくらかかるかを正確に知らなければなりません。

朗報は、ブラウザはパッケージを 1 つも使わずにこの仕事をやってこられたということです。btoa() は 2000 年代前半から搭載され、TextEncoder は 1 十年前に本物の Unicode テキストを誠実なバイトに変えるようになり、Baseline 2025 の波で、プラットフォームはついに URL 安全なアルファベットのオプション付きで、バイト配列を直接エンコードする Uint8Array.toBase64() を加えました。この記事は判断マップです:どの仕事にどのツール、鋭い縁はどこに隠れているか(すべて同じ境界に遡れます)、そして実際に Base64 の生成を求められる場所向けの具体的なレシピ。

エンコーダーの選び方

もう唯一の「the」エンコーダーなど存在しません。そして、間違ったものを取りに行くのが、定番バグが生まれる経路です。下表が、判断ツリーの全体です:

状況 取るべきもの
素の ASCII テキスト、使い捨ての値 btoa(text)
アクセント付き、絵文字、CJK を含む本物のテキスト new TextEncoder().encode(text)、その後 btoa または toBase64
バイトがすでに Uint8Array にある 2025 年以降のブラウザでは bytes.toBase64()、それ以外はチャンク化 btoa ブリッジ
URL、JWT、ファイル名 toBase64({ alphabet: 'base64url', omitPadding: true })
古いブラウザ、または共有コードベース js-base64、または定番の TextEncoder + btoa レシピ

表の下のパターン:btoa() が読むのは 1 バイト文字のみなので、ASCII でないものは、まずバイト配列にならなければならず、モダンな API は、まさにそのバイト配列を軸に作られました。「テキストがバイトになり、バイトが Base64 になる」を頭に入れておけば、この記事のすべてのレシピは、名前が違う同じ 2 ステップです。

btoa と Latin1 の境界

btoa(stringToEncode) - バイナリ文字列を ASCII 文字列へ - は元のエンコーダーで、大事なのはすべてのブラウザに利用可能です(Chrome 4、Firefox 1、Safari 3、IE 10 以降、すべてのワーカーのスコープ、そして Node はバージョン 16 から)。その契約には 1 つの条項があり、そこがすべて壊れる場所です:入力の各文字のコードポイントは 0 から 255 の間になければならない。この関数は UTF-8 バイトではなくコードポイントを読むので、「é」(コードポイント 233)はすーっと通り抜け、「你」(コードポイント 20320)は 1 文字もエンコードされる前に DOMException の一種 InvalidCharacterError を投げます。境界は「ASCII」ではなく、「Unicode」ではなく、ちょうど 256 で、下端の制御文字も含みます - NUL バイトのエンコードは合法で意味があり、この関数がそもそも存在する理由の 1 つです。

完全な挙動を、一行ずつ:

入力 結果
"Hello, World!" "SGVsbG8sIFdvcmxkIQ==" - 教科書通りのケース
""(空文字列) "" - 入力がなければ出力もない
"\u0000"(NUL) "AA==" - 制御文字も第一級市民
"a\u00e9z"(é、コードポイント 233) "Yel6" - Latin1 の範囲全体が通る
"\u0100"(コードポイント 256) InvalidCharacterError を投げる - 境界の 1 歩先
"h\u4f60"(你、コードポイント 20320) InvalidCharacterError を投げる - 絵文字も同様で、それはすべて 255 を大きく超えるため

実践的な注意が 2 つあります。エラーメッセージはエンジンごとに異なります - Firefox は「String contains an invalid character」と言い、Chrome は文字列が「contains characters outside of the Latin1 range」といいます - なので、防衛的なコードでは例外の名前でキャッチします。そして例外が投げられるのは、問題の最初の文字で、末尾ではありません:btoa は文字列の半分をエンコードして謝る、なんてことはしません。Latin1 の挙動を意図的に使いたいとき(0-255 のコードポイントからあえて作られたバイト文字列をエンコードするとき)は、この関数はあなたが求めたことをそのままやっています。そして上記の表が、その全人格です。

バイトのブリッジ

ここで問題が変わります:本物のデータ - テキストの UTF-8 バイト、ファイルの内容、canvas の出力 - はどうやって btoa() の入力に入るのか?答えは「bytes bridge」です:各文字が 1 バイトの値を保持する JavaScript の文字列。デコーダーが生成するのと同じトリックで、btoa はそれをネイティブに理解します。素朴なバージョンはループです:

function bytesToBase64 (bytes) {
  let binary = '';
  for (let i = 0; i < bytes.length; i += 1) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary);
}

正しいですが、ループの中での文字列連結は大きなファイルでは遅く、人気のある近道 - 配列全体を 1 回の呼び出しで引数として渡す String.fromCharCode.apply(null, bytes) - には急峻な断崖があります。関数呼び出しには引数の数に上限があり、それは最初の 1 メガバイトのずっと前に到達します:

const big = new Uint8Array(1000000);
btoa(String.fromCharCode.apply(null, big));
// RangeError in Firefox: "too many arguments provided for a function call"
// RangeError in Chrome: "Maximum call stack size exceeded"

ファイルアップロード機能を守ってきた、これ以上ない修正は、ブリッジをチャンクで渡ることです:数千文字ずつ渡して、結果を結合します:

function bytesToBase64Chunked (bytes) {
  const CHUNK = 0x8000;
  const parts = [];
  for (let i = 0; i < bytes.length; i += CHUNK) {
    parts.push(String.fromCharCode.apply(null, bytes.subarray(i, i + CHUNK)));
  }
  return btoa(parts.join(''));
}

各チャンクは apply しても安全なほど小さく、subarray はコピーなしのビューを与え、join はループが生んだのと同じバイナリ文字列を生成します。次に硬貨の裏側、テキスト側です。あらゆる本物のテキストにおいて、TextEncoder - プラットフォームの UTF-8 エンコーダーで、Firefox 18、Chrome 38、Safari 10.1 以降、どこでも利用可能 - がブリッジが仕事をする前に、あなたの文字列を誠実なバイトに変えます:

const bytes = new TextEncoder().encode('hello 你好');
const base64 = bytesToBase64Chunked(bytes);
console.log(base64); // "aGVsbG8g5L2g5aW9"

その出力は、「hello 你好」が配線上で本当に姿です:6 つの ASCII バイトと、2 つの漢字のための 6 つの UTF-8 バイトで、すべて同じ表示できる仮面を被っています。テキストが UTF-8 でなければ - そして Web 上ではたいていそうです - まず別の文字コードが必要で、それはその文字コードを話す場所でエンコードするということ、たいていサーバーです。TextEncoder はあえて推測することを拒否しますが、それは正しいです。

2025 年の近道:Uint8Array.toBase64

すでに Uint8Array を持っているなら、ブリッジは回り道です。新しい ECMAScript(ES2026)の機能が配列を直接エンコードするからです:bytes.toBase64(options)。Chrome 140、Edge 140、Firefox 133、Safari 18.2、Node 25、Deno 2.5 に搭載されました - デコード側の兄弟と同じ Baseline 2025 の波 - 2 つのオプションを受け取り、それをプラットフォーム上で最も万能なエンコーダーにします。1 つ目は alphabet:"base64"(デフォルト)か "base64url"。2 つ目は omitPadding:true にすると末尾の = が落とされます。多くの URL 親和的な利用者が望む形です。オプションにそれ以外のものを渡すと TypeError が投げられます。これは、あなたのタイポに対して API が丁寧であるという証拠です:

const bytes = new Uint8Array([251, 255]);
console.log(bytes.toBase64()); // "+/8="
console.log(bytes.toBase64({ omitPadding: true })); // "+/8"
console.log(bytes.toBase64({ alphabet: 'base64url' })); // "-_8="

この 2 バイトは、アルファベットに対して最大限に無礼になるよう選ばれています:標準モードでは + と / を生むので、最後の行は base64url に切り替えたときに変更されるものをまさに示しています。パフォーマンスは静かなボーナスです:最新 Firefox では、10 メガバイトのエンコードに toBase64 で約 5 ミリ秒かかりますが、上記の文字列ブリッジ経路は約 15 倍かかります。その過程で巨大な中間文字列を構築するためです。古いブラウザでは、ブリッジは数メガバイト未満のすべてのために完全に現役です - そして上記のチャンク化バージョンが、前節の理由によりあなたが望むものです。

URL 安全な出力

Base64 には、+、/、= が破壊を引き起こす場所のための専用バリアントがあります。壊れたコードの多くは URL に遭遇しただけの標準 Base64 なので、専用のセクションに値します。クエリ文字列では + はスペース、パスでは / は区切り文字、そして = はいくつかの位置ではパーセントエンコードを要求します。RFC 4648 の 5 節にある URL とファイル名で安全なアルファベット - base64url - は、その 2 文字を - と _ に交換し、データ長は受信側で通常わかっているので、パディングを完全に落とすことも許可します。出力はパーセント記号を 1 個も使わずに、URLSearchParams、パスセグメント、フラグメント、ファイル名を通過します。

2025 年の API では、これはオプションオブジェクト 1 つです:

const params = new URLSearchParams();
params.set('payload', bytes.toBase64({ alphabet: 'base64url', omitPadding: true }));
console.log(params.toString()); // "payload=-_8" - パーセントエンコードは一切なし

古いブラウザでは、btoa でエンコードしたあとに変換します。置換 2 つとトリム 1 つで仕事は全部です:

function toUrlBase64 (base64) {
  return base64
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}
console.log(toUrlBase64(btoa('hi?/x'))); // "aGk_L3g"

チャネルを清潔に保つためのルールが 3 つあります。チャネルごとにアルファベットを 1 つ選び、それを貫くこと - + と - を混ぜた値はどちらの系統にも属さず、どのデコーダーもあなたがどちらを意味したかと推測したりしません。パディングは契約であって提案ではありません:落とすなら、受信側はパディングなしの値に備えていなければならず、残すなら、受信側はそれで詰まってはなりません(ブラウザは寛容ですが、いくつかの JSON スキーマはそうではありません)。そして、この交換は可逆で無損失だということを覚えておいてください - - と _ は、+ と / が占めるのと同じアルファベットの 62 番目と 63 番目の位置にマッピングされるので、より親しみやすい組を選んでも何も失われません。

画像を旅させる:data URL

ブラウザにおける Base64 の、最も古くて最も目に見える用途は data URL です:data:、任意のメディアタイプ、任意の ;base64 フラグ、カンマ、それからペイロード。テキストのペイロードはパーセントエンコード、バイナリのペイロード - 画像、フォント、オーディオ - は Base64 で、ブラウザは HTTP リクエストゼロでレンダリングします。ユーザーがたった今選んだ画像ファイルには、FileReader が代わりにエンコードして、出来上がった URL を手渡してくれます:

const reader = new FileReader();
reader.onload = () => {
  console.log(reader.result); // "data:image/png;base64,iVBORw0KGgo..."
  imageElement.src = reader.result;
};
reader.readAsDataURL(file);

結果は出来合いの src で、localStorage に保存できる値でもあり、JSON 本体で送れる値でもあります。画像が代わりに canvas にある場合 - スクリーンショット、処理済み写真、生成されたチャート - canvas.toDataURL() は最も初期のブラウザリリースからこの仕事を担っており、フォーマットの選択もでき、lossy なフォーマットでは品質の指定までできます:

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.drawImage(photo, 0, 0);
const pngUrl = canvas.toDataURL('image/png');
const jpegUrl = canvas.toDataURL('image/jpeg', 0.8);

計画を立てるべき落とし穴が 3 つあります。第一に、tainted キャンバスのルール:CORS の許可なしにクロスオリジンの画像を canvas に描画した場合、ピクセルを読み戻すすべての試み - toDataURL を含む - は SecurityError を投げます。修正は、crossOrigin = 'anonymous' で画像を読み、サーバーが正しいヘッダーを送ることを確認することです。第二に、quality 引数は PNG では無視され、JPEG(と WebP)でだけ意味を持ちます -「なぜ私の PNG が大きいの」の一般的な原因です。第三に、そして最大の:ペイロードはファイルより約 33% 大きく、ページの中で文字列として置かれます。ブラウザを出ない画像には、無料の代替があります - オブジェクト URL で、Blob を一切エンコードせずに包みます:

const objectUrl = URL.createObjectURL(blob);
imageElement.src = objectUrl;
URL.revokeObjectURL(objectUrl); // 使い終わったら

ここから生まれる分担:ページにとどまるものにはオブジェクト URL、コピー、保存、またはテキストとして送らなければならないものには data URL。どちらも第一級市民で、単に解決している問題が異なるだけです。

JWT の構築と署名

ブラウザでトークンを生成するとして - セルフホストの認証フロー、デモ、サーバーレスのフロントエンド - コンパクトな JWS フォーマットは base64url セグメント 3 つです:ヘッダー、ペイロード、署名で、どこにもパディングがありません。署名は Web Crypto API が担当し、エンコーディングは 2 セクション前の URL 安全な出力そのものです:

const encoder = new TextEncoder();
const segment = (bytes) =>
  bytes.toBase64({ alphabet: 'base64url', omitPadding: true });
const header = segment(encoder.encode(JSON.stringify({ alg: 'HS256', typ: 'JWT' })));
const payload = segment(encoder.encode(JSON.stringify({ sub: '1234567890', name: 'John Doe' })));
const key = await crypto.subtle.importKey(
  'raw',
  encoder.encode('shared-secret'),
  { name: 'HMAC', hash: 'SHA-256' },
  false,
  ['sign']
);
const signature = segment(
  new Uint8Array(
    await crypto.subtle.sign('HMAC', key, encoder.encode(header + '.' + payload))
  )
);
const token = header + '.' + payload + '.' + signature;

配管より大切な詳細が 2 つあります。署名はちょうど header + '.' + payload をカバーします - 生のセグメントで、JSON ではありません - そのため、どちらかの部分に編集が入るとトークンは無効になります。これがすべてにおいてポイントです。そして crypto.subtle.sign は生の ArrayBuffer を返すため、セグメントエンコーダーの前に 1 行の Uint8Array ラップがあります。RSA ベースのトークンでは、RS256 とキーペアでフローは同一です。公開鍵を JWK としてエクスポートすると(crypto.subtle.exportKey('jwk', key))、数値メンバー - n、e、秘密鍵には d、p、q - は自動的にパディングなしの base64url として出てきます。セキュリティ上の注意はどのトークンでも同じです:alg: "none" のヘッダーは検証をスキップする要求であり、時間クレーム(exp、nbf)は強制されなければならず、同じ audience に対して HMAC と RSA の両方を受け入れるサーバーは、古典的なキー混同の扉を開けてしまいます。正しくエンコードし、正しく署名し、受信側で検証してください。

認証ヘッダー

Web で最もシンプルな認証スキームは、Base64 が何で何でないかについて、最も啓発的です。HTTP Basic は Authorization: Basic を送った後、username:password の Base64 を送ります - 呼び出し 1 回で、ユーザー名とパスワードは(願わくば)プレーンテキストなので、バイトのブリッジは不要です:

const credentials = btoa('alice:secret123');
fetch('/api/me', {
  headers: { Authorization: 'Basic ' + credentials }
});
// Authorization: Basic YWxpY2U6c2VjcmV0MTIz

そして、1 行に収まる教訓があります:Base64 は暗号化ではありません。上記のヘッダーは alice:secret123 まで atob 呼び出し 1 回で、攻撃者にも、ログを読む誰にもそうなので、Basic 認証は HTTPS のみで許容され、そこで輸送が実際の保護となり、Base64 は単なるフォーマットになります。1 リクエストより長い寿命を持つものには、トークンベースのスキームを優先してください:Bearer トークンも単一のヘッダーですが、その秘密がヘッダーに一切載る必要のないランダムな値で、失効させることもできます。2 つの間のエンコーディングの選択は自明です - どちらも btoa かプレーンテキスト - しかしセキュリティ上の選択はそうではなく、意図して行うべきです。

ファイルを入れ、テキストを出す

アップロードは、33% の税が実額で提示される場所です。ファイルはたいていページで最大のものだからです。道は 2 つあり、最初に選ぶべきなのはデフォルトです:マルチパートのフォームデータ。FormData はファイルを生バイトとして標準の本体に乗せ、フレームリングはブラウザが行い、絵の中のどこにも Base64 はありません - サイズ税もなく、中間文字列もなく、バイトは読まれたままサーバーへストリームされます:

const form = new FormData();
form.append('upload', file);
await fetch('/api/upload', { method: 'POST', body: form });

2 番目の道は、ファイルを文字列として JSON 本体で届くことを要求する API のためのものです - いくつかのサーバーレス関数、いくつかのモバイルバックエンド、いくつかのレガシーサービス。そこではエンコーディングは 1 ファイル 1 行で、コストは税が言う通りです:5 メガバイトのファイルは 6.7 メガバイトの文字列になり、それが JSON に直列化され、それが送られます。写真なら問題ありませんが、動画にはつらいです:

const bytes = new Uint8Array(await file.arrayBuffer());
const body = JSON.stringify({
  name: file.name,
  content: bytes.toBase64()
});
await fetch('/api/upload-json', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body
});

その道の大きなファイルでは、1 回の呼び出しで巨大な文字列を 1 つ作らないでください - スライスで作り、各スライスは 3 バイトの倍数にしてください。その整列こそがトリックを合法にするものです:3 バイトの倍数はパディングなしで 4 文字のきれいな倍数にエンコードされるので、独立にエンコードされたスライスを連結すると、ちょうどファイル全体のエンコーディングになり、パディングを持つのは最後のスライスだけです:

async function encodeLargeFile (file) {
  const bytes = new Uint8Array(await file.arrayBuffer());
  const SLICE = 3 * 1000 * 1000;
  const parts = [];
  for (let i = 0; i < bytes.length; i += SLICE) {
    parts.push(bytes.subarray(i, i + SLICE).toBase64());
  }
  return parts.join('');
}

同じ整列の考えが、Base64 文字列を任意の位置で分割して、その断片が自分だけでデコードできると思い込むべきではない理由です - 3 バイトのグループが原子であり、その真ん中で切ると、つり上がった断片が残るからです。ダウンロードは鏡像です:生成されたファイルには、小さいものはダウンロードリンクの data URL で出せますが、実体のあるものには Blob とオブジェクト URL が健全な経路です。ブラウザは最初からペイロード全体を文字列として運ぶ必要がないからです。

状態の保存と共有

Base64 が本物の仕事をする、テキストのみのチャネルがもう 2 つあります。1 つ目はストレージです:localStorage と sessionStorage は文字列を保持するので、構造化データやバイナリデータは入る前にエンコードされます。往復はエンコード 1 回とデコード 1 回で、ストレージのバグはほぼいつも 2 つの間の文字コードの不一致なので、両側を一緒に見る価値があります:

const state = { theme: 'dark', draft: 'hello' };
const packed = new TextEncoder().encode(JSON.stringify(state));
localStorage.setItem('app-state', new Uint8Array(packed).toBase64());
const raw = atob(localStorage.getItem('app-state'));
const bytes = Uint8Array.from(raw, (c) => c.codePointAt(0));
const state = JSON.parse(new TextDecoder().decode(bytes));

ただし、予算をちゃんと決めましょう:オリジンには localStorage としてだいたい 5 メガバイト与えられ、保存された文字列はデータより 33% 太く、ページが開いている間、その文字列はメモリの中で UTF-16 としても生き、また 2 倍の長さになります。3 メガバイトのアセットは 4 メガバイトのストレージと 8 メガバイトのメモリで、これが「小さな」機能をクォータエラーにする経路です。2 つ目のチャネルは URL 自体です:共有リンク、ディープリンク、OAuth の state はすべて、コピー & ペーストを生き延びられる場所に構造化データを望みます。レシピはコンパクトな state、JSON、それからパディングなしの base64url で、値にはパーセントエンコードが一切不要になります - そして URL 全体を 2,000 字以内に保ってください。古いクライアント、プロキシ、ログ記録ツールが不安になり始めるからです。

メールと MIME

Base64 は Web より古く、その本拠地はメールです。Content-Transfer-Encoding: base64 付きの MIME 添付ファイルは、バイナリファイルがテキストプロトコルの中で同乗する方法で、メッセージ形式の古い 76 文字の行長制限から生まれた慣習を知る価値があります:エンコードされた本体を 1 行 76 文字で折り返すこと。ブラウザは SMTP を送れませんが、メールの仕事は 2 つやります - バックエンドのリレーが送る MIME 本体を構築することと、受信したメッセージの添付ファイルを表示すること - で、どちらもエンコーディングに触れます。折り返し自体は 2 行の関数で、操作の順序が重要です:まずエンコード、それから折り返し。なぜなら btoa は入力の中の改行で例外を投げないからです - 改行をペイロードのバイトとしてエンコードし、あなたの行の切れ目が出力の中に入ってしまうからです:

function wrapForMime (base64, width) {
  const w = width || 76;
  return base64.match(new RegExp('.{1,' + w + '}', 'g')).join('\r\n');
}

受信側が無料です:atob は標準挙動の一部として ASCII 空白をスキップするので、折り返された MIME 本体は改行ごと、届いたまま正確にデコードされ、戻す手順は不要です。ウェブメールクライアントや添付ファイルピッカーを作っているなら、その 1 つの非対称性 - エンコーダーはきれいな行を生成しなければならず、デコーダーはどうでもいい - が、1 文における MIME の物語全体です。

ライブラリに頼るべきとき

上記のネイティブなツールがあれば、ライブラリはほとんど必要ありません。正直な指針はこれです:デフォルトはプラットフォームに任せ、本当の要求が何かを指しているときだけパッケージを追加します。コードベースで実際に現れる 3 つ:

js-base64(npm install js-base64)が汎用です:UTF-8 文字列を第一級市民として扱う、小さな純粋な JavaScript のトランスコーダー - CJK 文字列に Base64.encode をやれば、UTF-8 の踊りを代わりにやってくれます - しかも、エンコーディングと同様にデコーディングにも有用で、decode では両方のアルファベットを受け入れ、isValid チェックも同梱します。2025 年の API がないブラウザを対象にして、1 つの import で文字列とバイトの両方をカバーしたいなら、これが正解です:

import { Base64 } from 'js-base64';
const encoded = Base64.encode('小飼弾'); // "5bCP6aO85by+" - UTF-8 は代行済み
const decoded = Base64.decode('5bCP6aO85by-'); // 標準も URL 安全も同じように読める
const valid = Base64.isValid(encoded); // true

base64-js がバイト中心です:Uint8Array に対する fromByteArray と toByteArray、依存関係なし、古い browserify エコシステムの主力で、コードが型付き配列に暮らしていて、エンコーディングをバイトの純粋な関数にしたいなら、今も立派な選択肢です。そしてライブラリが欲しい理由が「2025 年の API は好きだが、2025 年のブラウザを要求できない」なら、答えは Base64 パッケージではなくポリフィルです:core-js(そしてそれを引き込む Babel プリセット)が Uint8Array.fromBase64 と仲間たちを実装するので、新スタイルのコードを 1 回書いて、古いエンジンでのギャップをシムに埋めてもらえます。制約で選んでください - 古いブラウザ、文字列の利便性、バイトの純粋性 - 癖では選びません。

開発者に何時間もかけた落とし穴

  • コードポイント 255 を超える文字を含む文字列に btoa を呼び出すこと。例外を投げ、壊すことはせず、最初の違反者で止まります。修正はいつも同じです:まず TextEncoder、次にブリッジ。
  • 大きな配列での fromCharCode.apply の断崖。100 万の引数は、両方の主要エンジンで RangeError です。ブリッジをチャンク化するか、toBase64 へ移行してください。
  • 最も傷つく場所でサイズ税を忘れること:ストレージ。localStorage 中のファイルはファイルより 33% 大きく、クォータはオリジン単位で、アプリが保存する他のすべてと共有されています。
  • クエリ文字列と遭遇した標準 Base64。+ はスペースとして届き、/ はパスを壊し、バグ報告は「API が不安定」と言います。URL 安全な出力、パディングなしで、このバグのジャンル丸ごとが消えます。
  • サービス間で揃わないパディング。あるゲートウェイは = を残し、別のゲートウェイは剥ぎ、三つ目は再び足します。受信側は両方の形に備えなければならず、契約はどちらが正かを言うべきです。
  • Base64 を錠前として扱うこと。それは直列化形式で、プレーンテキストから関数呼び出し 1 分の距離にあり、セキュリティレビューでの「Base64 でエンコード済み」は所見であって、コントロールではありません。
  • バイナリ文字列をメモリモデルとして持つこと。デコードされた、またはエンコードされた 1 メガバイトは UTF-16 で 2 メガバイトとして乗っています。Uint8Array なら 1 メガバイトで保持できます。大きなペイロードでは、端から端までバイトを型付き配列に保ってください。
  • 二重エンコード。すでに Base64 だった値が再びエンコードされ、利用者が 1 回デコードすると、データの代わりに文字の文字列が得られます。疑わしいときは、包む前にチェックしてください - アルファベットの中にあって有効なパディングを持つ文字列は悪臭です。
  • きれいにデコードできたからといって JWT のペイロードを信頼すること。デコード可能は本物であることではありません。クレームを 1 つでも読む前に、正しいキーと正しいアルゴリズムで署名を検証してください。

パフォーマンス:100 万バイトの価格

ブラウザでの Base64 は、以前高かったところが安くなりました。そして予算は 1 つの項目の代わりに 3 つの項目を持っています。CPU:最新 Firefox では、Uint8Array.toBase64 は 10 メガバイトを約 5 ミリ秒でエンコードしますが、チャンク化された btoa ブリッジは約 15 倍かかります - btoa が遅いからではなく、ブリッジが途中で巨大な中間文字列を構築するためです。エンコーディングの予算がミリ秒単位ならネイティブメソッドを使い、2 キロバイトの設定オブジェクトをエンコードするなら、どちらも知覚の閾値以下です。帯域:これが恒久税です - エンコードするバイトごとに、配線上では 1.33 バイトのコストがかかり、さらに輸送が追加するフレームリング分。エンコーディングを「最適化」する前に、転送を計測してください。メモリ:エンコードされた文字列はあなたが作る最大の一時アロケーションで、5 メガバイトのファイルなら 6.7 メガバイトの文字列、つまりページがそれを保持している間、約 13.4 メガバイトの UTF-16 メモリです。実践的な帰結は計算から導かれます:大きなエンコーディングをスライスして、1 つの文字列が巨大にならないようにし、文字列が存在し次第中間バイトを解放し、バイトが表示可能である必要がなかったならオブジェクト URL とマルチパートを優先し、メインスレッドが滑らかにスクロールし続けなければならないなら、数メガバイトの仕事は Web Worker に移しましょう。この形式はほぼ 40 年の歴史があります。プラットフォームがついに追いつきました。

ブラウザがエンコードを学んだ方法

エンコーダーには歴史があり、あなたが受け継ぐ遺物を説明してくれます。btoa -「バイナリから ASCII へ」。名前は文字通りで、atob は同じ言葉を逆にしただけ - は 2011 年に HTML 仕様に書き込まれ、すでにそれを搭載していたブラウザから逆解析されました:Firefox は 2004 年から、Safari 3、Chrome 4。Internet Explorer は、このブランドらしいことに、バージョン 10 の 2012 年まで両方の関数をスキップし、その 1 つの欠如こそが、10 年間の JavaScript が手作りの Base64 テーブルと、Unicode 向けの 1 つの特別な呪文 btoa(unescape(encodeURIComponent(str))) で溢れている理由です。それは動きました - encodeURIComponent はパーセントエスケープされた UTF-8 を生成し、unescape はそれをバイト文字列に変えた - しかし、それは言語が非推奨と宣告した組の 1 つ、unescape() の上に構築されており、純粋な慣性だけでブラウザのコードの中で何年も生き延びました。原理のある修正はエンコーディング標準とともにやってきます:TextEncoder と TextDecoder、Firefox 18(2013)、Chrome 38(2014)、Safari 10.1(2017)、そしてあらゆるバージョンの IE には一切なし - もう 1 つの IE の欠落、もう 10 年間の回避策。Node.js が物語のサーバー側半分を語ります:1 日目から Base64 付きの Buffer を持っていましたが、atob と btoa をグローバルとして持ったのは 2021 年のバージョン 16 だけで、それまでは 2 つの小さな npm シムが荷を担っていました。そして 2024 年後半から 2025 年にかけて、言語自体が Base64 を出荷しました - Firefox 133(2024 年 11 月)、Safari 18.2(2024 年 12 月)、Chrome 140(2025 年 9 月)、Node 25(2025 年 10 月)の Uint8Array.toBase64 と仲間たち - そしてその機能は Baseline 2025 とマークされました - プラットフォームが 20 年間ヘルパーで近似してきたのと同じ機能セットが、今や標準です。記事の末尾にあるおもしろい豆知識は、たいてい各パーツが到着するまでにどれほどかかったかについてのものです。

ご存知でしたか?

  • 関数名はフレーズです:btoa は「バイナリから ASCII へ」、atob は「ASCII からバイナリへ」です。方向性が名前のなかにあるので、この組は 2000 年代から自己文書化されてきました。
  • コンピューティングの歴史で最もエンコードされた文字列はおそらく「hello」です:btoa('hello') は aGVsbG8= で、世界中のすべてのチュートリアル、テストスイート、面接のホワイトボードの出力です。
  • すべての有効な Base64 文字列は、パディングを含む長さが 4 の倍数です。= 文字は指紋です:1 つあれば最後のグループが 2 バイトを持っていたことを意味し、2 つあれば 1 バイトです。
  • MIME とほとんどのコマンドラインツールにある 76 文字の行折り返しは、メッセージ形式の行長が制限を決めていたメール時代からの遺産です。その数字は、すべてが速くなった 30 年間を生き延びました。
  • 「Data URI」は引退した名前です。WHATWG が、URI から URL への名称の大統一の一環として「data URL」に改名したため、仕様、ブログ記事、パッケージ名は同じ段落の中でさまざまな綴りを使います。
  • btoa('') は '' を返します:空の入力は空の出力を生み、パディングもなければ特別なケースもない - 文字がゼロの唯一の Base64 文字列です(その長さ 0 は、なお 4 の倍数です)。
  • canvas は toDataURL で写真を data URL に変えられます - IE 9、Firefox 2、Safari 4 から存在する機能で、「モダン」と考えている Web プラットフォームの大部分より古い - そして <img> タグと FileReader で往復させて戻せます。
  • WebSocket のハンドシェイクは SHA-1(key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11) を Base64 でエンコードし、その GUID は RFC の固定定数で、素の HTTP サーバーがうっかりハンドシェイクを完成させてしまえないように、まさにそのために選ばれました。

これからどこへ

ブラウザでのエンコーディングという技芸全体が 1 ページに収まります:生れた本来の目的である、素の 1 バイトのケースには btoa;あらゆるブラウザでの本物のテキストとファイルには、TextEncoder とチャンク化ブリッジ;アルファベットとパディングのオプション付きで、モダンで直接的な経路には Uint8Array.toBase64;そして URL で暮らすすべてには、パディング付きでもなしでもよい URL 安全なバリアント。残りは判断です:33% の税を払う前にそれを知らなければならず、バイトが大きい間はそのまま型付き配列に保ち、まずエンコードしてそれから折り返し、直列化形式を錠前と呼ぶのはやめましょう。チャネルが生バイトを運べるなら、バイトを取りなさい - Base64 は表示できるテキストだけを許す道のためのものです。そして今、料金をどう払えばよいかが正確にわかるようになりました。

旅のもう半分 - これらの文字列の 1 つを受け取り、その中からバイト、テキスト、意味を取り出すこと - は、JavaScript での Base64 デコードのコンパニオンガイド(下記にリンク)で詳しく扱っています。

最終更新: 2026-10-09

関連記事: JavaScript/Browser での Base64 デコード:完全ガイド