🦔

バニラJavaScriptフォームバリデーション

に公開1

はじめに

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

junerjuner

エラーメッセージを自由に変える

setCustomValidity() でできます!

https://developer.mozilla.org/ja/docs/Web/API/HTMLInputElement/setCustomValidity

条件が複雑なチェック

メッセージは前述のでできますし、入力単位でチェックしてメッセージを設定することでできます。

なんなら カスタム要素にしてしまって ElementInternals を使えばだいたい何でもできます。

https://developer.mozilla.org/ja/docs/Web/API/ElementInternals

https://developer.mozilla.org/ja/docs/Web/API/Web_components/Using_custom_elements