Back

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

フレームワーク不要: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');
}

よくある質問

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

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

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

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

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

フォームフィールドを動的に追加するには、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