はじめに#
Webアプリケーションにおいて、ユーザーからデータを受け取る「フォーム」は最も重要な要素の一つです。お問い合わせフォーム、ログインフォーム、会員登録フォームなど、あらゆる場面でフォームが使われています。
JavaScriptを使うことで、フォームの値をリアルタイムに取得・操作したり、送信前にデータの妥当性をチェック(バリデーション)したりできます。適切なバリデーションを実装することで、ユーザー体験の向上とデータ品質の確保を両立できます。
本記事では、以下の内容を初心者向けにわかりやすく解説します。
- フォーム要素の取得と値の操作
- submitイベントの制御
- HTMLの組み込みバリデーション
- 制約検証APIによるカスタムバリデーション
- エラーメッセージの表示方法
- 実践的なフォームバリデーションの実装例
なお、DOM操作やイベント処理の基本については、JavaScriptのDOMの基本と要素取得の方法およびJavaScriptのイベント処理の基本と実践例で詳しく解説していますので、あわせてご覧ください。
フォーム要素の取得と基本操作#
フォーム要素へのアクセス方法#
JavaScriptでフォームを操作するには、まずフォーム要素を取得する必要があります。主な取得方法を見ていきましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 1. document.formsを使用する方法
// name属性で取得
const form1 = document.forms["contactForm"];
// インデックスで取得(ページ内の最初のフォーム)
const form2 = document.forms[0];
// 2. querySelectorを使用する方法(推奨)
const form3 = document.querySelector("#contactForm");
const form4 = document.querySelector("form.login-form");
// 3. getElementByIdを使用する方法
const form5 = document.getElementById("contactForm");
|
フォーム要素を取得すると、HTMLFormElementオブジェクトが返されます。このオブジェクトには、フォームに含まれる入力要素にアクセスするためのプロパティやメソッドが用意されています。
フォーム内の入力要素へのアクセス#
フォーム内の入力要素には、elementsプロパティを通じてアクセスできます。
1
2
3
4
5
6
|
<form id="userForm">
<input type="text" name="username" id="username">
<input type="email" name="email" id="email">
<input type="password" name="password" id="password">
<button type="submit">送信</button>
</form>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
const form = document.querySelector("#userForm");
// elementsプロパティでフォーム内の要素にアクセス
console.log(form.elements); // HTMLFormControlsCollection
// name属性で要素を取得
const usernameInput = form.elements["username"];
const emailInput = form.elements["email"];
// インデックスでも取得可能
const firstInput = form.elements[0];
// フォーム内の要素数を取得
console.log(form.elements.length); // 4(ボタンも含む)
|
入力値の取得と設定#
各入力要素の値はvalueプロパティで取得・設定できます。
1
2
3
4
5
6
7
8
9
10
11
12
|
const form = document.querySelector("#userForm");
const usernameInput = form.elements["username"];
// 値の取得
const currentValue = usernameInput.value;
console.log(currentValue);
// 値の設定
usernameInput.value = "新しい値";
// 入力欄をクリア
usernameInput.value = "";
|
様々な入力タイプの値取得#
入力タイプによって値の取得方法が異なる場合があります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<form id="sampleForm">
<!-- テキスト入力 -->
<input type="text" name="name" value="山田太郎">
<!-- チェックボックス -->
<input type="checkbox" name="agree" checked>
<!-- ラジオボタン -->
<input type="radio" name="gender" value="male" checked>
<input type="radio" name="gender" value="female">
<!-- セレクトボックス -->
<select name="prefecture">
<option value="tokyo">東京</option>
<option value="osaka" selected>大阪</option>
</select>
<!-- テキストエリア -->
<textarea name="message">お問い合わせ内容</textarea>
</form>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
const form = document.querySelector("#sampleForm");
// テキスト入力の値
const name = form.elements["name"].value; // "山田太郎"
// チェックボックスの状態(true/false)
const isAgreed = form.elements["agree"].checked; // true
// ラジオボタンの選択値
const gender = form.elements["gender"].value; // "male"
// セレクトボックスの選択値
const prefecture = form.elements["prefecture"].value; // "osaka"
// テキストエリアの値
const message = form.elements["message"].value; // "お問い合わせ内容"
|
submitイベントの制御#
submitイベントの基本#
フォームが送信されるとき、submitイベントが発生します。このイベントをリスナーで捕捉することで、送信前にバリデーションを実行したり、送信をキャンセルしたりできます。
1
2
3
4
5
6
7
8
9
10
|
const form = document.querySelector("#contactForm");
form.addEventListener("submit", (event) => {
console.log("フォームが送信されました");
// デフォルトの送信動作をキャンセル
event.preventDefault();
// ここでバリデーションや非同期送信処理を実行
});
|
event.preventDefault()を呼び出すと、フォームの通常の送信動作(ページ遷移やリロード)がキャンセルされます。これにより、JavaScriptで送信処理を完全に制御できます。
フォームデータの取得#
FormDataオブジェクトを使うと、フォームの全データを簡単に取得できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
form.addEventListener("submit", (event) => {
event.preventDefault();
// FormDataオブジェクトを作成
const formData = new FormData(form);
// 特定のフィールドの値を取得
const username = formData.get("username");
const email = formData.get("email");
// すべてのエントリーを反復処理
for (const [name, value] of formData.entries()) {
console.log(`${name}: ${value}`);
}
// オブジェクトに変換
const data = Object.fromEntries(formData.entries());
console.log(data); // { username: "...", email: "...", ... }
});
|
JavaScriptからフォームを送信する#
JavaScriptからプログラム的にフォームを送信する方法もあります。
1
2
3
4
5
6
7
8
9
10
11
|
const form = document.querySelector("#myForm");
// 通常の送信(submitイベントが発生しない)
form.submit();
// submitイベントを発生させて送信(推奨)
form.requestSubmit();
// 特定の送信ボタンを指定して送信
const submitButton = form.querySelector('button[type="submit"]');
form.requestSubmit(submitButton);
|
submit()メソッドはsubmitイベントを発生させずに送信するため、イベントリスナーでのバリデーションがスキップされます。一方、requestSubmit()メソッドはsubmitイベントを発生させるため、バリデーションが正しく実行されます。
HTMLの組み込みバリデーション#
バリデーション属性#
HTML5では、JavaScriptを使わずにフォームバリデーションを実装できる属性が用意されています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<form id="registrationForm">
<!-- 必須入力 -->
<input type="text" name="username" required>
<!-- メールアドレス形式 -->
<input type="email" name="email" required>
<!-- 文字数制限 -->
<input type="text" name="nickname" minlength="2" maxlength="20">
<!-- 数値の範囲制限 -->
<input type="number" name="age" min="18" max="100">
<!-- 正規表現パターン -->
<input type="text" name="zipcode" pattern="[0-9]{3}-[0-9]{4}"
title="郵便番号は「123-4567」の形式で入力してください">
<button type="submit">登録</button>
</form>
|
主なバリデーション属性は以下の通りです。
| 属性 |
説明 |
required |
入力必須にする |
minlength |
最小文字数を指定 |
maxlength |
最大文字数を指定 |
min |
数値の最小値を指定 |
max |
数値の最大値を指定 |
pattern |
正規表現パターンを指定 |
type |
入力形式を指定(email、url、numberなど) |
CSS疑似クラスによるスタイリング#
バリデーション状態に応じてスタイルを変更できるCSS疑似クラスがあります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/* 有効な入力 */
input:valid {
border: 2px solid #4CAF50;
}
/* 無効な入力 */
input:invalid {
border: 2px solid #f44336;
}
/* ユーザーが操作した後の無効な入力 */
input:user-invalid {
border: 2px solid #f44336;
background-color: #ffebee;
}
/* 必須フィールド */
input:required {
border-left: 3px solid #ff9800;
}
/* 範囲内の値 */
input:in-range {
background-color: #e8f5e9;
}
/* 範囲外の値 */
input:out-of-range {
background-color: #ffebee;
}
|
:user-invalid疑似クラスは、ユーザーが実際に操作した後にのみ適用されるため、ページ読み込み直後に空のフィールドがエラー表示されるのを防げます。
組み込みバリデーションの限界#
HTMLの組み込みバリデーションは便利ですが、以下の制限があります。
- エラーメッセージのカスタマイズが難しい
- 複雑な条件(パスワード一致確認など)に対応できない
- ブラウザ間でエラーメッセージの表示が異なる
これらの制限を克服するために、JavaScriptによるカスタムバリデーションが必要になります。
制約検証APIによるカスタムバリデーション#
制約検証APIとは#
制約検証API(Constraint Validation API)は、フォームの検証状態を確認したり、カスタムエラーメッセージを設定したりするためのJavaScript APIです。
validityプロパティ#
各入力要素はvalidityプロパティを持ち、ValidityStateオブジェクトを返します。このオブジェクトには、様々な検証状態を示すプロパティが含まれています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
const emailInput = document.querySelector('input[type="email"]');
// ValidityStateオブジェクトのプロパティ
console.log(emailInput.validity.valid); // 全体的に有効かどうか
console.log(emailInput.validity.valueMissing); // required属性があり値が空
console.log(emailInput.validity.typeMismatch); // typeに合わない値
console.log(emailInput.validity.patternMismatch);// patternに一致しない
console.log(emailInput.validity.tooShort); // minlength未満
console.log(emailInput.validity.tooLong); // maxlength超過
console.log(emailInput.validity.rangeUnderflow); // min未満
console.log(emailInput.validity.rangeOverflow); // max超過
console.log(emailInput.validity.stepMismatch); // stepに合わない
console.log(emailInput.validity.badInput); // ブラウザが解析できない
console.log(emailInput.validity.customError); // カスタムエラーが設定されている
|
checkValidity()とreportValidity()#
フォーム全体または個別の要素の妥当性をチェックするメソッドです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
const form = document.querySelector("#myForm");
const emailInput = document.querySelector('input[name="email"]');
// 個別要素の妥当性チェック
if (emailInput.checkValidity()) {
console.log("メールアドレスは有効です");
} else {
console.log("メールアドレスが無効です");
}
// フォーム全体の妥当性チェック
if (form.checkValidity()) {
console.log("すべての入力が有効です");
} else {
console.log("無効な入力があります");
}
// reportValidity()はブラウザのエラーメッセージも表示する
if (!form.reportValidity()) {
console.log("エラーがユーザーに報告されました");
}
|
setCustomValidity()によるカスタムエラー#
setCustomValidity()メソッドを使って、独自のエラーメッセージを設定できます。
1
2
3
4
5
6
7
8
9
10
11
12
|
const passwordInput = document.querySelector('input[name="password"]');
const confirmInput = document.querySelector('input[name="confirmPassword"]');
confirmInput.addEventListener("input", () => {
if (confirmInput.value !== passwordInput.value) {
// カスタムエラーを設定
confirmInput.setCustomValidity("パスワードが一致しません");
} else {
// エラーをクリア(空文字を設定)
confirmInput.setCustomValidity("");
}
});
|
空文字列を設定するとエラーがクリアされます。カスタムエラーが設定されている限り、その要素は無効な状態となります。
入力イベントでのリアルタイムバリデーション#
inputイベントを使って、ユーザーが入力するたびにリアルタイムでバリデーションを実行できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
const emailInput = document.querySelector('input[name="email"]');
const errorSpan = document.querySelector("#emailError");
emailInput.addEventListener("input", () => {
if (emailInput.validity.valid) {
// 有効な場合はエラーをクリア
errorSpan.textContent = "";
emailInput.classList.remove("error");
} else {
// 無効な場合はエラーメッセージを表示
showError();
}
});
function showError() {
if (emailInput.validity.valueMissing) {
errorSpan.textContent = "メールアドレスを入力してください";
} else if (emailInput.validity.typeMismatch) {
errorSpan.textContent = "有効なメールアドレスを入力してください";
}
emailInput.classList.add("error");
}
|
カスタムバリデーションの実装#
バリデーション関数の作成#
複雑なバリデーションロジックは、再利用可能な関数として定義すると便利です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
// バリデーション関数の例
const validators = {
// 必須チェック
required: (value) => {
return value.trim() !== "";
},
// メールアドレス形式チェック
email: (value) => {
const pattern = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
return pattern.test(value);
},
// 最小文字数チェック
minLength: (value, min) => {
return value.length >= min;
},
// 最大文字数チェック
maxLength: (value, max) => {
return value.length <= max;
},
// パスワード強度チェック
strongPassword: (value) => {
// 8文字以上、大文字・小文字・数字を含む
const pattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
return pattern.test(value);
},
// 電話番号形式チェック
phone: (value) => {
const pattern = /^0\d{1,4}-\d{1,4}-\d{4}$/;
return pattern.test(value);
}
};
// 使用例
const email = "test@example.com";
console.log(validators.email(email)); // true
const password = "Password123";
console.log(validators.strongPassword(password)); // true
|
フィールドごとのバリデーション設定#
フィールドごとにバリデーションルールを設定する仕組みを作ります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
// バリデーションルールの定義
const validationRules = {
username: {
required: true,
minLength: 3,
maxLength: 20,
messages: {
required: "ユーザー名は必須です",
minLength: "ユーザー名は3文字以上で入力してください",
maxLength: "ユーザー名は20文字以内で入力してください"
}
},
email: {
required: true,
email: true,
messages: {
required: "メールアドレスは必須です",
email: "有効なメールアドレスを入力してください"
}
},
password: {
required: true,
strongPassword: true,
messages: {
required: "パスワードは必須です",
strongPassword: "パスワードは8文字以上で、大文字・小文字・数字を含めてください"
}
}
};
// フィールドを検証する関数
function validateField(fieldName, value) {
const rules = validationRules[fieldName];
if (!rules) return { valid: true, message: "" };
// 必須チェック
if (rules.required && !validators.required(value)) {
return { valid: false, message: rules.messages.required };
}
// 空でない場合のみ他のチェックを実行
if (value.trim() === "") {
return { valid: true, message: "" };
}
// 最小文字数
if (rules.minLength && !validators.minLength(value, rules.minLength)) {
return { valid: false, message: rules.messages.minLength };
}
// 最大文字数
if (rules.maxLength && !validators.maxLength(value, rules.maxLength)) {
return { valid: false, message: rules.messages.maxLength };
}
// メールアドレス形式
if (rules.email && !validators.email(value)) {
return { valid: false, message: rules.messages.email };
}
// パスワード強度
if (rules.strongPassword && !validators.strongPassword(value)) {
return { valid: false, message: rules.messages.strongPassword };
}
return { valid: true, message: "" };
}
|
エラーメッセージの表示#
エラー表示用のHTML構造#
エラーメッセージを表示するための適切なHTML構造を用意します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<form id="registrationForm" novalidate>
<div class="form-group">
<label for="username">ユーザー名 <span class="required">*</span></label>
<input type="text" id="username" name="username"
aria-describedby="usernameError">
<span id="usernameError" class="error-message" aria-live="polite"></span>
</div>
<div class="form-group">
<label for="email">メールアドレス <span class="required">*</span></label>
<input type="email" id="email" name="email"
aria-describedby="emailError">
<span id="emailError" class="error-message" aria-live="polite"></span>
</div>
<div class="form-group">
<label for="password">パスワード <span class="required">*</span></label>
<input type="password" id="password" name="password"
aria-describedby="passwordError">
<span id="passwordError" class="error-message" aria-live="polite"></span>
</div>
<button type="submit">登録</button>
</form>
|
novalidate属性を<form>に追加すると、ブラウザの自動バリデーションが無効になり、JavaScriptで完全に制御できます。aria-live="polite"を設定することで、スクリーンリーダーがエラーメッセージの変更を読み上げます。
エラー表示用のCSS#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
.form-group {
margin-bottom: 1.5rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
}
.required {
color: #e53935;
}
input {
width: 100%;
padding: 0.75rem;
border: 2px solid #ccc;
border-radius: 4px;
font-size: 1rem;
transition: border-color 0.2s;
}
input:focus {
outline: none;
border-color: #2196F3;
}
input.valid {
border-color: #4CAF50;
}
input.invalid {
border-color: #e53935;
background-color: #ffebee;
}
.error-message {
display: block;
min-height: 1.5rem;
margin-top: 0.25rem;
font-size: 0.875rem;
color: #e53935;
}
|
エラー表示の実装#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
// エラーを表示する関数
function showFieldError(input, message) {
const errorElement = document.querySelector(`#${input.name}Error`);
if (errorElement) {
errorElement.textContent = message;
}
input.classList.remove("valid");
input.classList.add("invalid");
}
// エラーをクリアする関数
function clearFieldError(input) {
const errorElement = document.querySelector(`#${input.name}Error`);
if (errorElement) {
errorElement.textContent = "";
}
input.classList.remove("invalid");
input.classList.add("valid");
}
// 入力イベントでバリデーション
function setupFieldValidation(input) {
input.addEventListener("input", () => {
const result = validateField(input.name, input.value);
if (result.valid) {
clearFieldError(input);
} else {
showFieldError(input, result.message);
}
});
// フォーカスが外れたときもバリデーション
input.addEventListener("blur", () => {
const result = validateField(input.name, input.value);
if (!result.valid) {
showFieldError(input, result.message);
}
});
}
|
実践的なフォームバリデーションの実装例#
完全な実装例#
ここまでの内容を組み合わせた、実践的な会員登録フォームの実装例を紹介します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
|
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>会員登録フォーム</title>
<style>
* {
box-sizing: border-box;
}
body {
font-family: sans-serif;
max-width: 400px;
margin: 2rem auto;
padding: 0 1rem;
}
.form-group {
margin-bottom: 1.5rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
}
.required {
color: #e53935;
}
input {
width: 100%;
padding: 0.75rem;
border: 2px solid #ccc;
border-radius: 4px;
font-size: 1rem;
}
input:focus {
outline: none;
border-color: #2196F3;
}
input.valid {
border-color: #4CAF50;
}
input.invalid {
border-color: #e53935;
background-color: #ffebee;
}
.error-message {
display: block;
min-height: 1.25rem;
margin-top: 0.25rem;
font-size: 0.875rem;
color: #e53935;
}
button[type="submit"] {
width: 100%;
padding: 1rem;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
}
button[type="submit"]:hover {
background-color: #1976D2;
}
button[type="submit"]:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.success-message {
padding: 1rem;
background-color: #e8f5e9;
border: 1px solid #4CAF50;
border-radius: 4px;
color: #2e7d32;
text-align: center;
}
</style>
</head>
<body>
<h1>会員登録</h1>
<form id="registrationForm" novalidate>
<div class="form-group">
<label for="username">ユーザー名 <span class="required">*</span></label>
<input type="text" id="username" name="username"
aria-describedby="usernameError">
<span id="usernameError" class="error-message" aria-live="polite"></span>
</div>
<div class="form-group">
<label for="email">メールアドレス <span class="required">*</span></label>
<input type="email" id="email" name="email"
aria-describedby="emailError">
<span id="emailError" class="error-message" aria-live="polite"></span>
</div>
<div class="form-group">
<label for="password">パスワード <span class="required">*</span></label>
<input type="password" id="password" name="password"
aria-describedby="passwordError">
<span id="passwordError" class="error-message" aria-live="polite"></span>
</div>
<div class="form-group">
<label for="confirmPassword">パスワード(確認) <span class="required">*</span></label>
<input type="password" id="confirmPassword" name="confirmPassword"
aria-describedby="confirmPasswordError">
<span id="confirmPasswordError" class="error-message" aria-live="polite"></span>
</div>
<button type="submit">登録する</button>
</form>
<script>
// バリデーション関数
const validators = {
required: (value) => value.trim() !== "",
email: (value) => /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(value),
minLength: (value, min) => value.length >= min,
maxLength: (value, max) => value.length <= max,
strongPassword: (value) => /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/.test(value)
};
// バリデーションルール
const validationRules = {
username: {
required: true,
minLength: 3,
maxLength: 20,
messages: {
required: "ユーザー名を入力してください",
minLength: "3文字以上で入力してください",
maxLength: "20文字以内で入力してください"
}
},
email: {
required: true,
email: true,
messages: {
required: "メールアドレスを入力してください",
email: "有効なメールアドレスを入力してください"
}
},
password: {
required: true,
strongPassword: true,
messages: {
required: "パスワードを入力してください",
strongPassword: "8文字以上で、大文字・小文字・数字を含めてください"
}
},
confirmPassword: {
required: true,
messages: {
required: "パスワード(確認)を入力してください",
mismatch: "パスワードが一致しません"
}
}
};
// フィールドを検証
function validateField(fieldName, value, formData = {}) {
const rules = validationRules[fieldName];
if (!rules) return { valid: true, message: "" };
if (rules.required && !validators.required(value)) {
return { valid: false, message: rules.messages.required };
}
if (value.trim() === "") {
return { valid: true, message: "" };
}
if (rules.minLength && !validators.minLength(value, rules.minLength)) {
return { valid: false, message: rules.messages.minLength };
}
if (rules.maxLength && !validators.maxLength(value, rules.maxLength)) {
return { valid: false, message: rules.messages.maxLength };
}
if (rules.email && !validators.email(value)) {
return { valid: false, message: rules.messages.email };
}
if (rules.strongPassword && !validators.strongPassword(value)) {
return { valid: false, message: rules.messages.strongPassword };
}
// パスワード確認の一致チェック
if (fieldName === "confirmPassword" && formData.password !== value) {
return { valid: false, message: rules.messages.mismatch };
}
return { valid: true, message: "" };
}
// エラー表示
function showFieldError(input, message) {
const errorElement = document.querySelector(`#${input.name}Error`);
if (errorElement) {
errorElement.textContent = message;
}
input.classList.remove("valid");
input.classList.add("invalid");
}
// エラークリア
function clearFieldError(input) {
const errorElement = document.querySelector(`#${input.name}Error`);
if (errorElement) {
errorElement.textContent = "";
}
input.classList.remove("invalid");
if (input.value.trim() !== "") {
input.classList.add("valid");
} else {
input.classList.remove("valid");
}
}
// フォームの初期化
const form = document.querySelector("#registrationForm");
const inputs = form.querySelectorAll("input");
// 各フィールドにイベントリスナーを設定
inputs.forEach(input => {
input.addEventListener("input", () => {
const formData = Object.fromEntries(new FormData(form).entries());
const result = validateField(input.name, input.value, formData);
if (result.valid) {
clearFieldError(input);
} else {
showFieldError(input, result.message);
}
});
input.addEventListener("blur", () => {
const formData = Object.fromEntries(new FormData(form).entries());
const result = validateField(input.name, input.value, formData);
if (!result.valid) {
showFieldError(input, result.message);
}
});
});
// フォーム送信
form.addEventListener("submit", (event) => {
event.preventDefault();
const formData = Object.fromEntries(new FormData(form).entries());
let isValid = true;
let firstInvalidInput = null;
// すべてのフィールドを検証
inputs.forEach(input => {
const result = validateField(input.name, input.value, formData);
if (!result.valid) {
showFieldError(input, result.message);
isValid = false;
if (!firstInvalidInput) {
firstInvalidInput = input;
}
} else {
clearFieldError(input);
}
});
if (!isValid) {
// 最初のエラーフィールドにフォーカス
firstInvalidInput.focus();
return;
}
// バリデーション成功時の処理
console.log("送信データ:", formData);
// 成功メッセージを表示
form.innerHTML = '<div class="success-message">登録が完了しました</div>';
});
</script>
</body>
</html>
|
この実装例では以下の機能を含んでいます。
- リアルタイムバリデーション(入力中にエラーを表示)
- blurイベントでのバリデーション(フォーカスが外れたときにチェック)
- パスワード一致確認
- パスワード強度チェック(大文字・小文字・数字を含む8文字以上)
- エラー時に最初のエラーフィールドにフォーカス
- アクセシビリティ対応(aria-live、aria-describedby)
- 送信成功時のフィードバック
バリデーションのベストプラクティス#
フォームバリデーションを実装する際のベストプラクティスをまとめます。
ユーザー体験を重視する#
- リアルタイムフィードバックを提供し、エラーがあればすぐに知らせる
- エラーメッセージは具体的で、どう修正すればよいかを明示する
- 入力フォーマットの例を示す(例:「123-4567」形式)
- 成功時のフィードバックも忘れずに提供する
アクセシビリティを確保する#
aria-describedbyでエラーメッセージと入力欄を関連付ける
aria-live="polite"でスクリーンリーダーにエラーを通知する
- 色だけでなく、テキストやアイコンでもエラー状態を伝える
- キーボード操作に対応する
セキュリティを意識する#
- クライアント側のバリデーションだけで満足しない
- サーバー側でも必ずバリデーションを実装する
- クライアント側バリデーションはUXの向上が目的と認識する
コードの保守性を高める#
- バリデーションルールを設定オブジェクトとして分離する
- 再利用可能なバリデーション関数を作成する
- エラーメッセージを一箇所で管理する
まとめ#
本記事では、JavaScriptによるフォーム操作とバリデーションの基本を解説しました。
- フォーム要素は
document.querySelector()やdocument.formsで取得し、elementsプロパティで入力要素にアクセスできる
submitイベントとevent.preventDefault()で送信を制御できる
- HTMLの組み込みバリデーション属性(
required、patternなど)で基本的な検証が可能
- 制約検証API(
validity、checkValidity()、setCustomValidity())でカスタムバリデーションを実装できる
- エラーメッセージはアクセシブルな方法で表示する
- クライアント側バリデーションはUX向上が目的であり、サーバー側バリデーションも必須
フォームバリデーションは、ユーザー体験とデータ品質の両方に大きく影響する重要な機能です。本記事で紹介した手法を活用して、使いやすく安全なフォームを実装してみてください。
参考リンク#