【JavaScript入門】ファイルのローカルへの保存(controlArea.js)

 controlArea.jsエフェクトボタン群ローカル保存/削除機能 を束ねるモジュールです。ユーザーはワンクリックで

  • セピア・グレースケール・ぼかし といった加工を即時適用
  • 元に戻す/画像 + コメントを PC にダウンロード/作業内容を一掃

 という操作を行えます。保存処理では Blob + URL.createObjectURL() を用い、ブラウザ内だけで安全にファイルを書き出します。

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

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

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

/* ---------------------------------------------------
   controlArea.js
   - エフェクトボタン & ファイル操作
   - セピア / グレースケール / ぼかし / 元に戻す / 保存 / 削除
--------------------------------------------------- */
window.addEventListener('DOMContentLoaded', () => {

  /* ========== DOM 取得 ========== */
  const canvas = document.querySelector('#view');
  const ctx    = canvas.getContext('2d');

  // ボタン群
  const btnSepia = document.querySelector('#efSepia');
  const btnGray  = document.querySelector('#efGray');
  const btnBlur  = document.querySelector('#efBlur');
  const btnBack  = document.querySelector('#efBack');
  const btnSave  = document.querySelector('#efSave');
  const btnDel   = document.querySelector('#efDel');

  /* ========== 画像キャッシュ (元に戻す用) ========== */
  let cacheData = null;    // ImageData の退避先
  let cacheTime = null;    // Canvas 描画時刻

  // Canvas が更新されたら ImageData を確保
  function ensureCache(){
    const t = canvas.getAttribute('time');
    if (t && t !== cacheTime){
      cacheData = ctx.getImageData(0,0,canvas.width,canvas.height);
      cacheTime = t;
    }
  }
  const hasImage = () => canvas.getAttribute('time') !== null;

  /* ========== ボタンイベント ========== */
  // ▶ セピア
  btnSepia.addEventListener('click', () => {
    if(!hasImage()) return;
    ensureCache();
    applySepia();
  });

  // ▶ グレースケール
  btnGray.addEventListener('click', () => {
    if(!hasImage()) return;
    ensureCache();
    applyCanvasFilter(canvas, ctx, 'grayscale(100%)');   // imageEffects.js で定義
  });

  // ▶ ぼかし
  btnBlur.addEventListener('click', () => {
    if(!hasImage()) return;
    ensureCache();
    applyCanvasFilter(canvas, ctx, 'blur(4px)');
  });

  // ▶ 元に戻す
  btnBack.addEventListener('click', () => {
    if (cacheData) ctx.putImageData(cacheData, 0, 0);
  });

  // ▶ 保存(PNG + コメント TXT)
  btnSave.addEventListener('click', () => {
    if(!hasImage()) return;
    saveImagePNG();
    saveCommentTXT();
  });

  // ▶ 削除(表示・ローカル保存データを完全リセット)
  btnDel.addEventListener('click', () => {
    // 表示を初期ガイドに戻す
    document.querySelector('#noView').style.display = 'flex';
    canvas.style.display = 'none';

    // コメントクリア
    document.querySelector('#comment').value = '';

    // localStorage もクリア(storage.js)
    removeCanvasStorage();
    removeCommentStorage();
    restoreCommentFromStorage();   // デフォルト値に復帰
  });

  /* ========== 内部ユーティリティ ========== */
  // ▼ セピア変換:RGB→輝度→色調整の手書き演算
  function applySepia(){
    const img = ctx.getImageData(0,0,canvas.width,canvas.height);
    const d = img.data;
    for (let i = 0; i < d.length; i += 4){
      const y = 0.299*d[i] + 0.587*d[i+1] + 0.114*d[i+2]; // 輝度
      d[i]   = y * 240 / 255 | 0;  // R
      d[i+1] = y * 200 / 255 | 0;  // G
      d[i+2] = y * 145 / 255 | 0;  // B
    }
    ctx.putImageData(img, 0, 0);
  }

  // ▼ PNG をダウンロード
  function saveImagePNG(){
    const url = canvas.toDataURL('image/png');      // Base64 PNG
    triggerDownload(url, 'image.png');
  }

  // ▼ コメントを TXT でダウンロード
  function saveCommentTXT(){
    const txt  = document.querySelector('#comment').value;
    const blob = new Blob([txt], {type: 'text/plain'});  // MIME は text/plain
    const url  = URL.createObjectURL(blob);              // Blob → 一時 URL
    triggerDownload(url, 'comment.txt');
    URL.revokeObjectURL(url);                            // メモリを即解放
  }

  // ▼ a.download を仮生成して自動クリック
  function triggerDownload(href, filename){
    const a = document.createElement('a');
    a.href = href;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    a.remove();
  }

});

主要 API・命令一覧

命令/プロパティ働き主な使い方
canvas.getContext('2d')描画用の 2D コンテキスト取得画像加工に必須
ctx.getImageData() / putImageData()ピクセル操作セピア演算・元に戻す
applyCanvasFilter()CSS Canvas フィルター適用imageEffects.js で定義
canvas.toDataURL('image/png')Canvas → Base64 PNGダウンロード用 URL 生成
new Blob([data], {type})任意データをバイト列にラップコメント TXT 作成
URL.createObjectURL(blob)Blob → 一時 URL大容量でも高速生成
URL.revokeObjectURL(url)一時 URL を解放メモリリーク防止
<a>.downloadリンク先をファイル保存扱い自動 DL トリガーに使用

関数別の詳細解説

関数処理内容
ensureCache()Canvas の time 属性で変更有無を検知し、更新時のみ ImageData を保存。無駄なメモリコピーを防ぐ。
applySepia()各ピクセルを輝度 (Y) に変換して R/G/B を調整、セピア色を作る低レベル演算例。
applyCanvasFilter()CSS の filter を使って高速に加工(外部モジュール)。セピアのみピクセル計算にしたのはデモ目的。
triggerDownload(href, fn)DOM に <a download> を一瞬だけ挿入 → click() → 削除、でユーザー操作無しに保存ダイアログを出す。

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

  1. Blob + createObjectURL
    Base64 文字列より高速・省メモリ。作った URL は revokeObjectURL() で確実に解放。
  2. Canvas に time 属性を持たせる
    画像が描画されるたびに Date.now() をセットし、キャッシュと比較することで “元に戻す” 用のスナップショットを 1 度だけ取得できる。
  3. エフェクトは 2 系統
    低コストな CSS フィルターピクセル演算 の両方を示し、用途に応じて選択できる構成に。
  4. 削除ボタンは “完全リセット”
    UI 初期化・コメントクリア・localStorage 削除をワンストップで行い UX 向上。

 次回は フォーム送信をページ遷移なしで実現する sendArea.js を掘り下げ、Ajax で画像 + コメントをサーバへ送り、postListArea.js へ即時反映するリアルタイム投稿フローを実装します。