バニラJavaScriptフォームバリデーション
はじめに
HTML5のバリデーションとは
HTML5のフォームには、基本的なバリデーション機能があります。たとえば、required や min、maxlength といった属性や、:invalid や :valid などのCSSのルールを使えば、入力チェックと見た目のフィードバックが簡単にできます。
この機能はとても簡単で、よくある入力チェックには十分です。
でも、HTML5のバリデーションにはできないこともあります。
たとえば:
- 条件が複雑なチェック
- エラーメッセージを自由に変える
そういうときは、JavaScriptや外部ライブラリを使うことになります。
でも、まずはJavaScriptだけでフォームをチェックするやり方を学ぶことは、基本的な構文や考え方を理解するうえでとても良い練習になります。
JavaScriptを使えば、以下のようなことができます:
- 多言語対応のエラーメッセージ
- リアルタイムでの入力チェック
- 自分だけのエラー処理の実装
- 外部ライブラリに頼らずに作れる
とりあえず、フォームのHTMLを書きましょう。
フォームのHTML
HTMLフォームの基本構造
<form id="myForm" novalidate>
<div>
<label for="name">名前:</label>
<input type="text" id="name" name="name" placeholder="お名前を入力" required>
</div>
<div>
<label for="email">メールアドレス:</label>
<input type="email" id="email" name="email" placeholder="example@email.com" required>
</div>
<div>
<label for="phone">電話番号:</label>
<input type="tel" id="phone" name="phone" placeholder="090-1234-5678" pattern="^0\d{1,4}-\d{1,4}-\d{4}$" required>
</div>
<div>
<label for="contents">内容:</label>
<textarea id="contents" name="contents" rows="5" placeholder="お問い合わせ内容をご記入ください" required></textarea>
</div>
<div>
<input type="checkbox" id="terms" name="terms" required>
<label for="terms">個人情報保護方針に同意する</label>
</div>
<button type="submit">送信</button>
</form>
なぜ action や method を使わないのか
<form>タグに action や method を書いていない理由は、JavaScriptでフォームを完全にコントロールするためです。e.preventDefault() を使って送信をキャンセルし、JSでバリデーションと送信処理を自作するため、HTML側の設定は必要ありません。代わりに、novalidate 属性でブラウザのデフォルトUIを無効化しています。
電話番号の pattern 属性について
電話番号の <input> にある pattern="^0\d{1,4}-\d{1,4}-\d{4}$" は、正規表現を使って「0から始まり、ハイフン区切りの日本の電話番号形式(例: 090-1234-5678)」にマッチするかをチェックしています。pattern 属性を使うと、特定のフォーマットを簡単に制限できます。
次、フォームの要素を取得します
JavaScriptでの準備
form 要素の取得
const form = document.querySelector("#myForm");
submit イベントの設定
e.preventDefault() の意味
form.addEventListener("submit", (e) => {
e.preventDefault(); // ページの自動リロードを止める
});
JavaScriptでフォームを処理する場合は、e.preventDefault() を使ってページのリロードを防ぎます。これでJavaScriptだけでフォームを操作できます。
フィールドの取得と確認
全フィールドを取得する方法
const form = document.querySelector("#myForm");
form.addEventListener("submit", (e) => {
e.preventDefault();
const formFields = form.querySelectorAll("input, textarea");
console.log(formFields);
});
これで、formFields にすべての入力フィールドが入ります。
NodeList vs HTMLCollection
querySelectorAll() は NodeList を返します。NodeList は .forEach() や for...of などのループ処理が使える、イテラブル(反復可能)なオブジェクトです。これにより、フォーム内の全てのフィールドに対して簡単にループ処理ができます。
一方、e.target.elements や form.elements を使うと、HTMLCollection が返ってきます。これは NodeList に似ていますが、以下の点で少し扱いにくいです:
- イテラブルではない(古いブラウザでは forEach() が使えない)
- ライブコレクションなので、フォームの内容が変わると自動的に反映されます(便利なこともありますが、意図しない挙動になる場合もあります)
- Array のように見えても、実際には配列ではないため、配列のメソッド(map() など)は使えません
そのため、今回のように 明示的に取得した時点のフォームフィールドに対してバリデーション処理を行いたい場合は、querySelectorAll() の方が安全で扱いやすい です。
バリデーションとエラー表示
入力チェックとエラーメッセージ表示
form.addEventListener("submit", (e) => {
e.preventDefault();
const formFields = form.querySelectorAll("input, textarea");
// 以前のエラーメッセージを削除
form.querySelectorAll(".error-message").forEach((el) => el.remove());
let isFormValid = true;
formFields.forEach((field) => {
field.classList.remove("invalid");
if (!field.checkValidity()) {
isFormValid = false;
field.classList.add("invalid");
const error = document.createElement("div");
error.className = "error-message";
error.innerText = getErrorMessage(field);
field.parentNode.appendChild(error);
}
});
if (isFormValid) {
console.log("フォームの入力はOKです!");
// fetch() などでデータ送信ができます
}
});
エラーメッセージを返す関数
const getErrorMessage = (field) => {
if (field.validity.valueMissing) {
return "このフィールドは必須です。";
}
if (field.type === "email" && field.validity.typeMismatch) {
return "有効なメールアドレスを入力してください。";
}
if (field.name === "phone" && field.validity.patternMismatch) {
return "電話番号の形式が正しくありません(例: 090-1234-5678)。";
}
return "入力内容に誤りがあります。";
};
getErrorMessage関数の役割
この関数は、HTML5の .validity オブジェクトを使って、入力にどんなエラーがあるのかを判定し、それに応じたメッセージを返します。
たとえば:
- field.validity.valueMissing → 必須項目が空のとき
- field.validity.typeMismatch → メールアドレスが不正なとき
- field.validity.patternMismatch → 正規表現にマッチしないとき
.validity オブジェクトの中身
validityのプロパティ一覧と意味
{
valueMissing: true, // 必須なのに空
typeMismatch: false, // メールやURLが不正
patternMismatch: false, // 正規表現に合わない
tooShort: false, // minlength以下
tooLong: false, // maxlength以上
rangeUnderflow: false, // min未満
rangeOverflow: false, // max超え
stepMismatch: false, // stepに合わない
badInput: false, // 無効な入力(例: 数字欄に文字)
customError: false, // setCustomValidity() を使ったとき
valid: false // 全体として有効か
}
CSSで見た目のエラー表示
エラーの視覚的フィードバック
input.invalid,
textarea.invalid {
border: 2px solid #e63946;
background-color: #fff5f5;
}
.error-message {
color: #e63946;
font-size: 0.875rem;
margin-top: 4px;
}
赤いボーダーと背景色で、エラーのあるフィールドを目立たせます。エラーメッセージは赤字で表示します。
最後に
VueやReactでも、バリデーションの基本の考え方は同じです。
フォームの状態の扱い方が違うだけです。
HTMLとJavaScriptの基本的なバリデーションを理解すれば、どんな環境でも応用できます。
もし興味があれば、実際に動くCodePenの例もあります:
CodePenはこちら
Discussion
setCustomValidity() でできます!
メッセージは前述のでできますし、入力単位でチェックしてメッセージを設定することでできます。
なんなら カスタム要素にしてしまって ElementInternals を使えばだいたい何でもできます。