【JavaScript入門】画面遷移しないでファイルをサーバーに送信する(sendArea.js)

 sendArea.js「投稿する」ボタンを押した瞬間に画像バイナリ + コメント文字列を Ajax でサーバへ送信 し、ページ遷移なしでタイムライン (postListArea) を即時リロードするモジュールです。画像は <canvas> 上の内容を PNG Blob に変換、コメントはテキストで FormData に詰めます。送信完了後は成功トーストを表示し、投稿一覧を updatePostList()(別モジュール)で再取得するだけなので UX が途切れません。

フォルダ構成(該当部分)

node-js/
 └─ app/
     ├─ index.html
     ├─ style.css
     └─ js/
          ├─ common.js
          ├─ storage.js
          ├─ imageArea.js
          ├─ imageEffects.js
          ├─ controlArea.js
          ├─ commentArea.js
          ├─ sendArea.js   ← ★今回実装
          ├─ dateFormat.js
          └─ postListArea.js

sendArea.js ― フルコード & 詳細コメント

/* ---------------------------------------------------
   sendArea.js
   - 画像 + コメント をサーバーへ POST
   - 完了後タイムラインを更新
--------------------------------------------------- */
window.addEventListener('DOMContentLoaded', () => {

  /* === 対象 DOM === */
  const btnSend = document.querySelector('#send');     // 投稿ボタン
  const txtArea = document.querySelector('#comment');  // コメント欄
  const canvas  = document.querySelector('#view');     // プレビューキャンバス

  /* === クリックで非同期送信 === */
  btnSend.addEventListener('click', async () => {

    /* ---------- 1. 入力バリデーション ---------- */
    const comment = txtArea.value;
    const lenMax  = parseInt(txtArea.dataset.lenmax, 10);

    // 文字数チェック
    if (comment.length > lenMax){
      alert(`コメントが ${comment.length} 文字です。\n最大 ${lenMax} 文字を超えています。`);
      return;
    }
    // 画像チェック
    if (!canvas.getAttribute('time')){
      alert('画像が読み込まれていません。');
      return;
    }

    /* ---------- 2. Canvas → PNG Blob 変換 ---------- */
    // Canvas を Base64 → バイナリ配列 → Blob へ
    const base64 = canvas.toDataURL('image/png').split(',')[1];
    const bin    = atob(base64);                 // Base64 → binary string
    const buf    = new Uint8Array(bin.length);
    for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
    const blob   = new Blob([buf.buffer], {type: 'image/png'});

    /* ---------- 3. FormData に詰めて送信 ---------- */
    const fd = new FormData();
    fd.append('comment', comment);
    fd.append('image',   blob);                  // `<input type=file>` 相当

    try{
      const res = await fetch('/post', {method:'POST', body: fd});
      if (!res.ok) throw res;                    // ステータス 2xx 以外は例外
      await res.text();                          // サーバ応答 (ここでは文字列想定)

      updatePostList();                          // 🎉 タイムラインを再取得
      alert('投稿が完了しました!');
    }catch(err){
      alert('投稿に失敗しました。サーバーを確認してください。');
      console.error(err);
    }
  });

});

主要 API/命令まとめ

API / プロパティ働き用途/ポイント
canvas.toDataURL('image/png')Canvas → Base64 PNGクライアント側で画像データ取得
atob() / Uint8ArrayBase64 → バイナリ配列PNG Blob を生成する前処理
new Blob([...], {type})バイト列 → BlobMIME "image/png" を指定
URL.createObjectURL(blob)Blob → 一時 URL今回は直接送信なので未使用
FormData.append(key,val)マルチパートボディに追加ファイルも文字列も同列に扱える
fetch(url,{method:'POST',body:fd})Ajax POSTページ遷移せず送信
updatePostList()タイムライン再描画postListArea.js が提供

関数・処理フロー解説

処理段階主な関数/行詳細
1. バリデーションif (comment.length > lenMax)…上限超過コメントや画像未読込を即時ブロック
2. 画像変換canvas.toDataURL() → atob()クライアント JS だけで Canvas を PNG Blob 化
3. 送信FormData + fetchmultipart/form-data 形式で /post エンドポイントへ
4. 成功ハンドラupdatePostList()投稿完了後に一覧を Ajax 再取得して “瞬時に反映”
エラーハンドラcatch(err)ネットワーク/サーバ障害をアラート表示&ログ出力

実装ポイント & ベストプラクティス

  1. Base64 → Blob
    canvas.toBlob() を使うと非同期 API で簡潔に書けますが、Edge 古い版など対応外のブラウザもあるためここでは 手動変換 を示しています。モダン環境のみを想定する場合は canvas.toBlob() が簡単です。
  2. multipart/form-data
    ファイルとテキストを一緒に送りたい場合は FormData を必ず使用。Content-Type ヘッダーは fetch が自動付与 するので自分で指定しない。
  3. updatePostList() の分離
    投稿完了後の UI 更新は postListArea.js に切り出し、責務を明確化。テストや再利用が楽になります。
  4. 適切なエラーメッセージ
    res.ok のチェックを忘れると 500 などでも成功扱いになるので要注意。 catch ではログを出してデバッグしやすく。

これで ページリロードなしの投稿 機能が完成しました。
 次回は、 過去投稿のタイムライン表示 & いいね!ボタンのリアルタイム更新 を実装する postListArea.js を解説していきます。