フレームワーク不要: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でフォームを処理する際は、アクセシビリティを維持することを確認してください:
- フォーカス管理の維持:エラーを表示する際は、最初の無効なフィールドにフォーカスを設定する
- ARIA属性の使用:無効なフィールドに
aria-invalid="true"
を追加する - エラーメッセージの関連付け:
aria-describedby
を使用して入力とエラーメッセージを接続する - 明確な指示の提供:
<label>
要素と説明的なエラーメッセージを使用する - キーボードナビゲーションのサポート:すべてのフォームコントロールがキーボードでアクセス可能であることを確認する
アクセシブルなエラー処理の例:
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フォームが実際にどのように動作するかの理解を深めます。