フォームを使ったWebサイトでは、ユーザーの入力ミスを事前に防ぐことがとても重要です。
送信時にまとめてエラーを出すよりも、入力欄を離れたタイミングでチェックを行えば、その場で修正を促せるのでユーザビリティが格段に向上します。
その実装にぴったりなのが、JavaScriptのfocusout
イベントです。
focusoutとは?
focusout
イベントは、入力欄(input, textarea, selectなど)からフォーカスが外れた瞬間に発生するイベントです。
onfocus
:要素にフォーカスが当たったときonblur
/onfocusout
:要素からフォーカスが外れたとき
element.addEventListener('focusout', () => {
console.log('フォーカスが外れた!');
});
基本の使い方:入力チェック
<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の補助に最適で、ユーザー体験の改善に直結する
- フォームの精度やエラー防止を向上させる重要なテクニック!
コメント