12k
All articles

フレームワーク不要:Vanilla JavaScriptでフォーム入力を処理する

Vanilla JavaScriptでフォーム送信をキャプチャし、HTML5制約によるバリデーションとFormData APIを使った入力値の読み取り方を解説する。

OpenReplay Team
OpenReplay Team
フレームワーク不要:Vanilla JavaScriptでフォーム入力を処理する

フォームは、Web上でのユーザーインタラクションの基盤です。ReactやVueなどのフレームワークは便利な抽象化を提供しますが、vanilla JavaScriptでフォームを処理する方法を理解することで、完全な制御を得ることができ、不要な依存関係を排除できます。

このガイドでは、JavaScript のネイティブなフォーム処理について知っておくべきすべてを説明します。要素の選択や値の取得から、入力の検証、送信の処理まで網羅します。

重要なポイント

  • Vanilla JavaScriptは、フレームワークなしで完全なフォーム処理に必要なすべてのツールを提供します
  • event.preventDefault()を使用してフォーム送信の動作を制御します
  • HTML5制約検証を活用して、最小限のコードで基本的な検証を実装します
  • inputおよびblurイベントでリアルタイム検証を実装し、ユーザーエクスペリエンスを向上させます
  • FormData APIはフォーム値の収集と処理を簡素化します
  • カスタムフォーム検証を実装する際は、常にアクセシビリティを考慮してください

JavaScriptによる基本的なフォーム処理

まず、シンプルなHTMLフォームから始めましょう:

<form id="signup-form">
  <div>
    <label for="username">Username:</label>
    <input type="text" id="username" name="username">
  </div>
  <div>
    <label for="email">Email:</label>
    <input type="email" id="email" name="email">
  </div>
  <div>
    <label for="password">Password:</label>
    <input type="password" id="password" name="password">
  </div>
  <button type="submit">Sign Up</button>
</form>

フォーム要素の選択

フォームとその要素にアクセスする方法はいくつかあります:

// IDで選択(特定のフォームに推奨)
const form = document.getElementById('signup-form');

// querySelectorを使用
const form = document.querySelector('#signup-form');

// フォーム要素コレクションにアクセス
const forms = document.forms;
const signupForm = forms['signup-form']; // id/nameで
const firstForm = forms[0]; // インデックスで

送信イベントの取得

フォーム送信を処理するには、フォームにイベントリスナーを追加します:

const form = document.getElementById('signup-form');

form.addEventListener('submit', function(event) {
  // デフォルトのフォーム送信を防ぐ
  event.preventDefault();
  
  // ここでフォームデータを処理
  console.log('Form submitted!');
});

preventDefault()メソッドは、ブラウザがフォームを送信してページを更新することを停止し、次に何が起こるかを制御できるようにします。

フォーム入力値へのアクセス

フォーム入力値にアクセスする方法は複数あります:

方法1:個別要素へのアクセス

form.addEventListener('submit', function(event) {
  event.preventDefault();
  
  // 個別のフォーム要素から値を取得
  const username = document.getElementById('username').value;
  const email = document.getElementById('email').value;
  const password = document.getElementById('password').value;
  
  console.log(username, email, password);
});

方法2:form.elementsの使用

elementsプロパティは、すべてのフォームコントロールへのアクセスを提供します:

form.addEventListener('submit', function(event) {
  event.preventDefault();
  
  // elementsコレクション経由でアクセス
  const username = form.elements.username.value;
  const email = form.elements.email.value;
  const password = form.elements.password.value;
  
  console.log(username, email, password);
});

方法3:FormData APIの使用

FormData APIは、すべてのフォーム値を取得するクリーンな方法を提供します:

form.addEventListener('submit', function(event) {
  event.preventDefault();
  
  // フォームからFormDataオブジェクトを作成
  const formData = new FormData(form);
  
  // 個別の値にアクセス
  const username = formData.get('username');
  const email = formData.get('email');
  
  // 通常のオブジェクトに変換
  const formObject = Object.fromEntries(formData);
  console.log(formObject); // {username: '...', email: '...', password: '...'}
});

HTML5制約によるフォーム検証

現代のHTML5は、JavaScriptなしで動作する組み込み検証属性を提供します:

<form id="signup-form">
  <div>
    <label for="username">Username:</label>
    <input type="text" id="username" name="username" required minlength="3">
  </div>
  <div>
    <label for="email">Email:</label>
    <input type="email" id="email" name="email" required>
  </div>
  <div>
    <label for="password">Password:</label>
    <input type="password" id="password" name="password" required minlength="8" 
           pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).*$">
    <small>パスワードは大文字、小文字、数字を含む8文字以上である必要があります</small>
  </div>
  <button type="submit">Sign Up</button>
</form>

一般的な検証属性には以下があります:

  • required:フィールドは必須入力
  • minlength/maxlength:テキストの最小/最大長
  • min/max:数値入力の最小/最大値
  • pattern:マッチする正規表現パターン
  • type="email":メール形式の検証
  • type="url":URL形式の検証

制約検証APIの活用

JavaScriptは、ブラウザの組み込み検証システムにアクセスできます:

form.addEventListener('submit', function(event) {
  // 制約検証APIを使用してフォームが有効かチェック
  if (!form.checkValidity()) {
    // フォームが無効 - ブラウザにエラー表示を任せる
    return;
  }
  
  // ここに到達すれば、フォームは有効
  event.preventDefault();
  
  // 有効なフォームデータを処理
  console.log('Form is valid, processing data...');
});

特定の入力の有効性もチェックできます:

const emailInput = document.getElementById('email');

// メールが有効かチェック
if (emailInput.validity.valid) {
  console.log('Email is valid');
} else {
  // 特定の検証失敗をチェック
  if (emailInput.validity.typeMismatch) {
    console.log('Email format is incorrect');
  }
  if (emailInput.validity.valueMissing) {
    console.log('Email is required');
  }
}

カスタム検証とエラーメッセージ

HTML5検証は便利ですが、カスタム検証ロジックとエラーメッセージが必要な場合がよくあります:

form.addEventListener('submit', function(event) {
  event.preventDefault();
  
  const username = document.getElementById('username');
  const email = document.getElementById('email');
  const password = document.getElementById('password');
  
  // 以前のエラーメッセージをクリア
  clearErrors();
  
  let isValid = true;
  
  // カスタムユーザー名検証
  if (username.value.trim() === '') {
    displayError(username, 'ユーザー名は必須です');
    isValid = false;
  } else if (username.value.length < 3) {
    displayError(username, 'ユーザー名は3文字以上である必要があります');
    isValid = false;
  }
  
  // カスタムメール検証
  if (!isValidEmail(email.value)) {
    displayError(email, '有効なメールアドレスを入力してください');
    isValid = false;
  }
  
  // カスタムパスワード検証
  if (password.value.length < 8) {
    displayError(password, 'パスワードは8文字以上である必要があります');
    isValid = false;
  } else if (!/[A-Z]/.test(password.value)) {
    displayError(password, 'パスワードには大文字を少なくとも1つ含める必要があります');
    isValid = false;
  }
  
  // 有効な場合、フォームを処理
  if (isValid) {
    console.log('Form is valid, submitting...');
    // フォームデータを送信
  }
});

function isValidEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

function displayError(input, message) {
  const formControl = input.parentElement;
  const errorElement = document.createElement('div');
  errorElement.className = 'error-message';
  errorElement.textContent = message;
  formControl.appendChild(errorElement);
  input.classList.add('error-input');
}

function clearErrors() {
  document.querySelectorAll('.error-message').forEach(error => error.remove());
  document.querySelectorAll('.error-input').forEach(input => {
    input.classList.remove('error-input');
  });
}

inputイベントによるリアルタイム検証

より良いユーザーエクスペリエンスのために、ユーザーが入力している間に検証を行います:

const emailInput = document.getElementById('email');

emailInput.addEventListener('input', function() {
  if (this.value && !isValidEmail(this.value)) {
    this.setCustomValidity('有効なメールアドレスを入力してください');
  } else {
    this.setCustomValidity('');
  }
});

function isValidEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

inputイベントはユーザーが値を変更するたびに発生し、changeイベントは入力が変更された後にフォーカスを失ったときに発生します。

異なる入力タイプの処理

ラジオボタン

<div>
  <p>サブスクリプションプラン:</p>
  <label>
    <input type="radio" name="plan" value="free" checked> 無料
  </label>
  <label>
    <input type="radio" name="plan" value="premium"> プレミアム
  </label>
</div>
// 選択されたラジオボタンの値を取得
const selectedPlan = document.querySelector('input[name="plan"]:checked').value;

// 変更をリッスン
document.querySelectorAll('input[name="plan"]').forEach(radio => {
  radio.addEventListener('change', function() {
    console.log('Selected plan:', this.value);
  });
});

チェックボックス

<div>
  <p>興味のある分野:</p>
  <label>
    <input type="checkbox" name="interests" value="javascript"> JavaScript
  </label>
  <label>
    <input type="checkbox" name="interests" value="html"> HTML
  </label>
  <label>
    <input type="checkbox" name="interests" value="css"> CSS
  </label>
</div>
// チェックされたすべての値を取得
function getSelectedInterests() {
  const checkboxes = document.querySelectorAll('input[name="interests"]:checked');
  const values = Array.from(checkboxes).map(cb => cb.value);
  return values;
}

// フォーム送信時
form.addEventListener('submit', function(event) {
  event.preventDefault();
  const interests = getSelectedInterests();
  console.log('Selected interests:', interests);
});

セレクトドロップダウン

<div>
  <label for="country">国:</label>
  <select id="country" name="country">
    <option value="">国を選択してください</option>
    <option value="us">アメリカ合衆国</option>
    <option value="ca">カナダ</option>
    <option value="uk">イギリス</option>
  </select>
</div>
// 選択された値を取得
const country = document.getElementById('country').value;

// 変更をリッスン
document.getElementById('country').addEventListener('change', function() {
  console.log('Selected country:', this.value);
});

完全なフォーム処理の例

すべてを組み合わせた完全な例を見てみましょう:

document.addEventListener('DOMContentLoaded', function() {
  const form = document.getElementById('signup-form');
  
  // リアルタイム検証を設定
  setupRealTimeValidation();
  
  form.addEventListener('submit', function(event) {
    event.preventDefault();
    
    if (validateForm()) {
      // フォームデータでオブジェクトを作成
      const formData = new FormData(form);
      const userData = Object.fromEntries(formData);
      
      // 通常はここでデータをサーバーに送信
      console.log('Submitting user data:', userData);
      
      // API呼び出しをシミュレート
      submitFormData(userData)
        .then(response => {
          showSuccessMessage('アカウントが正常に作成されました!');
          form.reset();
        })
        .catch(error => {
          showErrorMessage('アカウントの作成に失敗しました。もう一度お試しください。');
        });
    }
  });
  
  function validateForm() {
    // 以前のエラーをクリア
    clearErrors();
    
    let isValid = true;
    const username = form.elements.username;
    const email = form.elements.email;
    const password = form.elements.password;
    
    // ユーザー名を検証
    if (username.value.trim() === '') {
      displayError(username, 'ユーザー名は必須です');
      isValid = false;
    } else if (username.value.length < 3) {
      displayError(username, 'ユーザー名は3文字以上である必要があります');
      isValid = false;
    }
    
    // メールを検証
    if (email.value.trim() === '') {
      displayError(email, 'メールは必須です');
      isValid = false;
    } else if (!isValidEmail(email.value)) {
      displayError(email, '有効なメールアドレスを入力してください');
      isValid = false;
    }
    
    // パスワードを検証
    if (password.value === '') {
      displayError(password, 'パスワードは必須です');
      isValid = false;
    } else if (password.value.length < 8) {
      displayError(password, 'パスワードは8文字以上である必要があります');
      isValid = false;
    } else if (!/[A-Z]/.test(password.value) || !/[a-z]/.test(password.value) || !/[0-9]/.test(password.value)) {
      displayError(password, 'パスワードには大文字、小文字、数字を含める必要があります');
      isValid = false;
    }
    
    return isValid;
  }
  
  function setupRealTimeValidation() {
    const inputs = form.querySelectorAll('input');
    
    inputs.forEach(input => {
      input.addEventListener('input', function() {
        // ユーザーが入力を開始したときにエラーをクリア
        const errorElement = this.parentElement.querySelector('.error-message');
        if (errorElement) {
          errorElement.remove();
          this.classList.remove('error-input');
        }
      });
    });
    
    // メール固有の検証
    form.elements.email.addEventListener('blur', function() {
      if (this.value && !isValidEmail(this.value)) {
        displayError(this, '有効なメールアドレスを入力してください');
      }
    });
  }
  
  // ヘルパー関数
  function isValidEmail(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }
  
  function displayError(input, message) {
    const formControl = input.parentElement;
    const errorElement = document.createElement('div');
    errorElement.className = 'error-message';
    errorElement.textContent = message;
    formControl.appendChild(errorElement);
    input.classList.add('error-input');
  }
  
  function clearErrors() {
    document.querySelectorAll('.error-message').forEach(error => error.remove());
    document.querySelectorAll('.error-input').forEach(input => {
      input.classList.remove('error-input');
    });
  }
  
  function showSuccessMessage(message) {
    const messageElement = document.createElement('div');
    messageElement.className = 'success-message';
    messageElement.textContent = message;
    form.parentElement.insertBefore(messageElement, form);
    
    // 3秒後に削除
    setTimeout(() => {
      messageElement.remove();
    }, 3000);
  }
  
  function showErrorMessage(message) {
    const messageElement = document.createElement('div');
    messageElement.className = 'error-banner';
    messageElement.textContent = message;
    form.parentElement.insertBefore(messageElement, form);
    
    // 3秒後に削除
    setTimeout(() => {
      messageElement.remove();
    }, 3000);
  }
  
  // API送信をシミュレート
  function submitFormData(data) {
    return new Promise((resolve, reject) => {
      // ネットワークリクエストをシミュレート
      setTimeout(() => {
        // デモ用に90%の成功率
        if (Math.random() > 0.1) {
          resolve({ success: true, message: 'User created' });
        } else {
          reject({ success: false, message: 'Server error' });
        }
      }, 1500);
    });
  }
});

フォームのアクセシビリティに関する考慮事項

JavaScriptでフォームを処理する際は、アクセシビリティを維持することを確認してください:

  1. フォーカス管理の維持:エラーを表示する際は、最初の無効なフィールドにフォーカスを設定する
  2. ARIA属性の使用:無効なフィールドにaria-invalid="true"を追加する
  3. エラーメッセージの関連付けaria-describedbyを使用して入力とエラーメッセージを接続する
  4. 明確な指示の提供<label>要素と説明的なエラーメッセージを使用する
  5. キーボードナビゲーションのサポート:すべてのフォームコントロールがキーボードでアクセス可能であることを確認する

アクセシブルなエラー処理の例:

function displayError(input, message) {
  const formControl = input.parentElement;
  const errorId = `${input.id}-error`;
  
  const errorElement = document.createElement('div');
  errorElement.id = errorId;
  errorElement.className = 'error-message';
  errorElement.textContent = message;
  
  formControl.appendChild(errorElement);
  
  // アクセシビリティ属性を追加
  input.setAttribute('aria-invalid', 'true');
  input.setAttribute('aria-describedby', errorId);
  
  // 最初の無効な入力にフォーカスを設定
  if (!document.querySelector('.error-input')) {
    input.focus();
  }
  
  input.classList.add('error-input');
}

よくある質問

JavaScriptでフォームをリセットするにはどうすればよいですか?

フォーム要素の組み込みresetメソッドを使用します:document.getElementById('myForm').reset();

送信前にフォームが有効かどうかを確認するにはどうすればよいですか?

制約検証APIのcheckValidityメソッドを使用します:const isValid = form.checkValidity();

フォームデータを非同期で送信するにはどうすればよいですか?

フォームデータを非同期で送信するには、FormDataオブジェクトと組み合わせてFetch APIを使用します。まず、フォームからFormDataインスタンスを作成します。次に、POSTメソッドでfetchを使用して送信します。レスポンスを処理し、送信中のエラーをキャッチすることを忘れないでください。

vanilla JavaScriptでファイルアップロードを処理するにはどうすればよいですか?

ファイルアップロードを処理するには、ファイル入力要素のfilesプロパティにアクセスし、選択されたファイルを取得し、fetchや他の方法で送信する前にFormDataオブジェクトに追加します。

inputイベントとchangeイベントの違いは何ですか?

`input`イベントは入力値が変更されるたびに発生し、`change`イベントは入力がフォーカスを失い、フォーカスを得てから値が変更された場合にのみ発生します。

JavaScriptでフォームフィールドを動的に追加するにはどうすればよいですか?

フォームフィールドを動的に追加するには、document.createElementを使用して新しい入力要素を作成し、そのtypeとnameを設定し、フォーム内のコンテナ要素に追加します。

まとめ

vanilla JavaScriptでのフォーム処理をマスターすることで、外部ライブラリに依存することなく、フォームを完全に制御できるようになります。このアプローチは依存関係を減らし、パフォーマンスを向上させ、Webフォームが実際にどのように動作するかの理解を深めます。

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers

We use cookies to improve your experience. By using our site, you accept cookies.