無理しないでゆっくり休んでね!

JavaScriptでフォーム入力ミスを防ぐ:focusoutイベントの使い方と実践例

フォームを使ったWebサイトでは、ユーザーの入力ミスを事前に防ぐことがとても重要です。

送信時にまとめてエラーを出すよりも、入力欄を離れたタイミングでチェックを行えば、その場で修正を促せるのでユーザビリティが格段に向上します。

その実装にぴったりなのが、JavaScriptのfocusoutイベントです。

focusoutとは?

focusoutイベントは、入力欄(input, textarea, selectなど)からフォーカスが外れた瞬間に発生するイベントです。

  • onfocus:要素にフォーカスが当たったとき
  • onblur / onfocusout:要素からフォーカスが外れたとき
element.addEventListener('focusout', () => {
  console.log('フォーカスが外れた!');
});

blurとの違いは、「バブリング(伝播)するかどうか」です。focusoutは親要素に届きます。

基本の使い方:入力チェック

<label>メールアドレス:</label>
<input type="email" id="email" required>
<span id="email-error" style="color:red;"></span>

<script>
  const emailInput = document.getElementById("email");
  const error = document.getElementById("email-error");

  emailInput.addEventListener("focusout", () => {
    const value = emailInput.value.trim();
    if (value === "" || !value.includes("@")) {
      error.textContent = "有効なメールアドレスを入力してください。";
    } else {
      error.textContent = "";
    }
  });
</script>

このように、フォーカスが外れた瞬間にバリデーションを行うことで、次の入力に進む前に問題を指摘できます。

blurとの違いは?

イベント発火タイミング親要素に伝播するか
blurフォーカスが外れたとき❌ 伝播しない
focusoutフォーカスが外れたとき✅ 親要素にも伝播する

つまり、複数の入力欄をまとめて監視したい場合はfocusoutが便利です。

以下のHTMLで2つの<input>があるとします。

<form id="my-form">
  <input type="text" name="first_name" placeholder="姓">
  <input type="text" name="last_name" placeholder="名">
</form>

🔹 blurを使った場合(各inputに個別でつける)

const inputs = document.querySelectorAll("input");
inputs.forEach(input => {
  input.addEventListener("blur", () => {
    console.log("blur発生:" + input.name);
  });
});

これは各inputに1個ずつイベントを付けている状態。

フォームが10個あれば10個分処理を定義しなければならない。

親要素(form)には伝わらない

🔸 focusoutを使った場合(formにまとめてつける)

document.getElementById("my-form").addEventListener("focusout", function (event) {
  if (event.target.tagName === "INPUT") {
    console.log("focusout発生:" + event.target.name);
  }
});

これはform全体でinputのフォーカス離脱をまとめて拾っている状態。

入力欄が増えても、コードはそのままでOK!

イメージ図で理解しよう。

+---------------------+
|   <form id="my-form">  ← 親要素
|   +-------------+       ← <input> 姓
|   |  input[1]   |       ← 個別の入力欄
|   +-------------+
|   +-------------+       ← <input> 名
|   |  input[2]   |
|   +-------------+
+---------------------+

blur:input[1] でしか発火しない(formには伝わらない)
focusout:input[1] → form にも伝わる!(イベントバブリング)

実用例:複数項目のエラー検出

<form id="contact-form">
  <input type="text" name="name" placeholder="お名前"><br>
  <input type="email" name="email" placeholder="メールアドレス"><br>
  <textarea name="message" placeholder="メッセージ"></textarea><br>
  <span id="form-error" style="color: red;"></span>
</form>

<script>
  const form = document.getElementById("contact-form");
  const errorBox = document.getElementById("form-error");

  form.addEventListener("focusout", function(event) {
    if (event.target.tagName === "INPUT" || event.target.tagName === "TEXTAREA") {
      if (event.target.value.trim() === "") {
        errorBox.textContent = "未入力の項目があります。";
      } else {
        errorBox.textContent = "";
      }
    }
  });
</script>

このように、フォーム全体にfocusoutをつけると、子要素のどの入力欄であってもバリデーションを統一して実行できます。

応用:入力が終わったらサジェスト表示

<label>都道府県:</label>
<input type="text" id="pref" placeholder="例:東京都">
<ul id="suggestions" style="display:none; padding:5px;">
  <li>東京都</li>
  <li>神奈川県</li>
  <li>千葉県</li>
</ul>

<script>
  const prefInput = document.getElementById("pref");
  const suggestion = document.getElementById("suggestions");

  prefInput.addEventListener("focusout", () => {
    if (prefInput.value.trim() === "") {
      suggestion.style.display = "none";
    } else {
      suggestion.style.display = "block";
    }
  });
</script>

この例では、入力後に何か処理(サジェスト、補助)を表示したいときに、focusoutを使ってトリガーにしています。

注意点:フォーカスが移った直後のDOM操作

focusoutイベントは、フォーカスが外れた瞬間に実行されるため、次にどこにフォーカスが移るかには関与しません。
そのため、モーダルやエラー表示の中に自動でフォーカスを移すような場合は、setTimeout()などで1フレーム待つのがコツです。

element.addEventListener("focusout", () => {
  setTimeout(() => {
    document.getElementById("next-input").focus();
  }, 10);
});

よくある使用シーンまとめ

シーンfocusoutの用途
入力チェック必須項目チェック、形式チェック(メール、電話番号)
UI補助値に応じて注意メッセージや補助入力を出す
自動補完サジェストリストの表示・非表示切替
エラー修正誘導入力ミスがあった項目に戻す指示を出す

サンプルコード

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>フォーカスアウト バリデーション Demo</title>
  <style>
    body {
      font-family: sans-serif;
      margin: 20px;
    }
    .input-group {
      margin-bottom: 20px;
    }
    .error {
      color: red;
      font-size: 0.9em;
      margin-top: 4px;
    }
  </style>
</head>
<body>

  <h2>商品登録フォーム</h2>

  <div class="input-group">
    <label>数量:</label><br>
    <input type="text" id="quantity" />
    <div class="error" id="error-quantity"></div>
  </div>

  <div class="input-group">
    <label>価格(円):</label><br>
    <input type="text" id="price" />
    <div class="error" id="error-price"></div>
  </div>

  <div class="input-group">
    <label>メールアドレス:</label><br>
    <input type="text" id="email" />
    <div class="error" id="error-email"></div>
  </div>

  <script>
    function validateQuantity() {
      const input = document.getElementById("quantity");
      const error = document.getElementById("error-quantity");
      const value = input.value.trim();

      if (!/^\d+$/.test(value)) {
        error.textContent = "整数で入力してください。";
      } else if (parseInt(value, 10) > 1000) {
        error.textContent = "1000以下で入力してください。";
      } else {
        error.textContent = "";
      }
    }

    function validatePrice() {
      const input = document.getElementById("price");
      const error = document.getElementById("error-price");
      const value = input.value.trim();

      if (!/^\d+(\.\d{1,2})?$/.test(value)) {
        error.textContent = "数字で入力してください。小数は2桁までです。";
      } else {
        error.textContent = "";
      }
    }

    function validateEmail() {
      const input = document.getElementById("email");
      const error = document.getElementById("error-email");
      const value = input.value.trim();

      if (!value.match(/^[^@\s]+@[^@\s]+\.[^@\s]+$/)) {
        error.textContent = "メール形式で入力してください。";
      } else {
        error.textContent = "";
      }
    }

    // それぞれの入力欄にフォーカスアウトイベントを追加
    document.getElementById("quantity").addEventListener("blur", validateQuantity);
    document.getElementById("price").addEventListener("blur", validatePrice);
    document.getElementById("email").addEventListener("blur", validateEmail);
  </script>

</body>
</html>

まとめ

  • focusoutは「入力欄から出たとき」に発火するイベント
  • blurと違ってバブリング(伝播)するため、親要素でまとめて監視できる
  • 入力チェックやUIの補助に最適で、ユーザー体験の改善に直結する
  • フォームの精度やエラー防止を向上させる重要なテクニック!

コメント

タイトルとURLをコピーしました