フォームの入力欄がフォーカスされたときにヘルプテキストを表示する、フォーカスが外れたときにバリデーションを実行する、モーダル内でフォーカスを閉じ込めるなど、フォーカスイベントはフォーム操作やアクセシビリティの実装に欠かせません。
この記事でわかること
・focus / blur イベントの基本
・focusin / focusout との違い(バブリング)
・element.focus() でプログラム的にフォーカスを移動する方法
・CSS :focus / :focus-within との使い分け
・tabindex でフォーカス可能にする方法
・relatedTarget でフォーカスの移動元・移動先を取得
・フォームバリデーション・フローティングラベル・フォーカストラップの実務パターン
・focus / blur イベントの基本
・focusin / focusout との違い(バブリング)
・element.focus() でプログラム的にフォーカスを移動する方法
・CSS :focus / :focus-within との使い分け
・tabindex でフォーカス可能にする方法
・relatedTarget でフォーカスの移動元・移動先を取得
・フォームバリデーション・フローティングラベル・フォーカストラップの実務パターン
フォーカスイベントの種類
| イベント | 発火タイミング | バブリング |
|---|---|---|
focus |
要素がフォーカスを受けたとき | × しない |
blur |
要素からフォーカスが外れたとき | × しない |
focusin |
要素がフォーカスを受けたとき | ○ する |
focusout |
要素からフォーカスが外れたとき | ○ する |
focus と blur の基本
JavaScript
const input = document.getElementById("email");
// フォーカスを受けたとき
input.addEventListener("focus", () => {
console.log("フォーカスされました");
});
// フォーカスが外れたとき
input.addEventListener("blur", () => {
console.log("フォーカスが外れました");
});
focus / blur は input、textarea、select、button、a などフォーカス可能な要素で使えます。
focusin / focusout(バブリングする版)
focus と blur はバブリングしないため、親要素で子要素のフォーカスをまとめて検知できません。バブリングする focusin / focusout を使えば、フォーム全体のフォーカスを親で一括管理できます。
focus: 親要素で検知できない
const form = document.getElementById("myForm");
// NG: focus はバブリングしないので親では発火しない
form.addEventListener("focus", () => {
console.log("子要素にフォーカス"); // 発火しない
});
focusin: 親要素で検知できる
const form = document.getElementById("myForm");
// OK: focusin はバブリングするので親で検知できる
form.addEventListener("focusin", (e) => {
console.log("フォーカスされた要素:", e.target.name);
});
form.addEventListener("focusout", (e) => {
console.log("フォーカスが外れた要素:", e.target.name);
});
| 比較項目 | focus / blur | focusin / focusout |
|---|---|---|
| バブリング | しない | する |
| イベント委任 | 使えない | 使える |
| 推奨場面 | 個別要素のフォーカス検知 | 親要素で複数フィールドを一括管理 |
フォーム全体でフォーカスイベントを管理するなら
focusin / focusout を使いましょう。個別の input に 1 つずつリスナーを設定するよりも効率的です。element.focus() でプログラム的にフォーカスを移動する
JavaScript
const input = document.getElementById("username");
// フォーカスを移動
input.focus();
// スクロールを防止してフォーカス
input.focus({ preventScroll: true });
// フォーカスを外す
input.blur();
ページ読み込み時に自動フォーカス
// DOMContentLoaded 後にフォーカス
document.addEventListener("DOMContentLoaded", () => {
document.getElementById("searchInput").focus();
});
// HTML の autofocus 属性でも可能
// <input id="searchInput" autofocus>
HTML の
autofocus 属性はページ読み込み時に 1 要素だけ自動フォーカスできます。ただし SPA ではページ遷移後に再発火しないため、JavaScript で focus() を明示的に呼ぶ必要があります。CSS :focus / :focus-within との使い分け
| やりたいこと | CSS | JavaScript |
|---|---|---|
| フォーカス時のスタイル変更 | :focus / :focus-visible ○ |
不要 |
| 親要素のスタイル変更 | :focus-within ○ |
不要 |
| フォーカス時にテキスト表示 | △ CSS のみでは限定的 | ○ focus イベント |
| フォーカスが外れたときにバリデーション | × | ○ blur イベント |
| フォーカスの移動先を判定 | × | ○ relatedTarget |
CSS :focus-within の例
.form-group {
border: 2px solid #ccc;
transition: border-color 0.2s ease;
}
/* 子要素にフォーカスがあるとき親のスタイルを変更 */
.form-group:focus-within {
border-color: #0284c7;
}
tabindex でフォーカス可能にする
div や span などのフォーカス不可能な要素に tabindex を設定すると、フォーカスを受け取れるようになります。
| tabindex の値 | 動作 |
|---|---|
0 |
Tab キーの自然な順序でフォーカス可能になる |
-1 |
Tab キーではフォーカスされないが、focus() でフォーカス可能 |
1 以上 |
指定した順序でフォーカス(非推奨: DOM 順と異なり混乱する) |
HTML
<!-- Tab キーでフォーカス可能 --> <div tabindex="0" class="card">カード1</div> <!-- JS の focus() でのみフォーカス可能 --> <div tabindex="-1" id="errorMessage">エラー: 入力が不正です</div>
tabindex="1" 以上は DOM の順序と Tab の順序が一致しなくなるため、アクセシビリティの観点から非推奨です。0 か -1 を使ってください。relatedTarget でフォーカスの移動元・移動先を取得する
JavaScript
const input = document.getElementById("email");
input.addEventListener("focus", (e) => {
console.log("フォーカス移動元:", e.relatedTarget); // 前にフォーカスされていた要素
});
input.addEventListener("blur", (e) => {
console.log("フォーカス移動先:", e.relatedTarget); // 次にフォーカスされる要素
});
relatedTarget は、フォーカスがフォーム外に移動したかどうかの判定に使えます。ドロップダウンメニューやオートコンプリートの「外クリックで閉じる」処理に活用できます。実務でよく使うパターン
フォーカスが外れたときにバリデーションを実行する
JavaScript
const emailInput = document.getElementById("email");
const errorMsg = document.getElementById("emailError");
emailInput.addEventListener("blur", () => {
const value = emailInput.value.trim();
if (value && !value.includes("@")) {
errorMsg.textContent = "有効なメールアドレスを入力してください";
emailInput.classList.add("is-invalid");
} else {
errorMsg.textContent = "";
emailInput.classList.remove("is-invalid");
}
});
フローティングラベル(フォーカスでラベルを移動)
CSS
.form-field {
position: relative;
}
.form-field label {
position: absolute;
top: 12px;
left: 12px;
color: #999;
transition: all 0.2s ease;
pointer-events: none;
}
/* フォーカス時 or 入力済みのとき: ラベルを上に移動 */
.form-field input:focus + label,
.form-field input:not(:placeholder-shown) + label {
top: -8px;
font-size: 12px;
color: #0284c7;
}
フローティングラベルは CSS の :focus と :placeholder-shown だけで実装できます。JavaScript は不要です。
バリデーションエラー時に最初のエラー要素にフォーカスを移動する
JavaScript
function validateForm(form) {
const errors = [];
form.querySelectorAll("[required]").forEach(field => {
if (!field.value.trim()) {
field.classList.add("is-invalid");
errors.push(field);
} else {
field.classList.remove("is-invalid");
}
});
// 最初のエラー要素にフォーカス
if (errors.length > 0) {
errors[0].focus();
return false;
}
return true;
}
フォーカストラップ(モーダル内にフォーカスを閉じ込める)
JavaScript
function trapFocus(container) {
const focusable = container.querySelectorAll(
'a, button, input, textarea, select, [tabindex]:not([tabindex="-1"])'
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
container.addEventListener("keydown", (e) => {
if (e.key !== "Tab") return;
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
});
first.focus();
}
フォーカストラップはモーダルやドロワーメニューのアクセシビリティに必須です。WAI-ARIA のダイアログパターンに基づくベストプラクティスです。
関連記事
- クリックイベントの設定方法 — addEventListener・バブリング・イベント委任
- マウスイベントの使い方 — mouseenter・mousemove
- ハンバーガーメニューの実装方法 — フォーカストラップの実例
- classList の使い方完全ガイド
- querySelector / querySelectorAll の使い方
よくある質問
Qfocus と focusin の違いは何ですか?
A
focus はバブリングしないため、親要素でリスナーを設定しても子要素のフォーカスは検知できません。focusin はバブリングするため、親要素で子要素のフォーカスをまとめてキャッチできます。フォーム全体のフォーカス管理には focusin が便利です。Qdiv や span にフォーカスを当てるにはどうすればよいですか?
A
tabindex="0" を設定するとフォーカス可能になります。Tab キーでのフォーカスが不要で JavaScript の focus() でのみフォーカスしたい場合は tabindex="-1" を使います。QCSS の :focus と :focus-visible の違いは?
A
:focus はすべてのフォーカスに適用されます。:focus-visible はキーボード操作でフォーカスした場合のみ適用され、マウスクリックでは適用されません。ボタンのフォーカスリングをキーボード操作時のみ表示したい場合に使います。Qblur イベントで relatedTarget が null になります。
A
relatedTarget はフォーカスの移動先要素を返しますが、フォーカスがページ外(ブラウザの URL バーなど)に移動した場合は null になります。null チェックを入れてから使ってください。Qフォーカスリング(アウトライン)を消してもよいですか?
Aアクセシビリティの観点から消すべきではありません。キーボードユーザーがどの要素にフォーカスがあるか分からなくなります。デザイン上消したい場合は
:focus-visible で代替のフォーカススタイルを必ず設定してください。まとめ
JavaScript のフォーカスイベントの使い方を整理しました。
- 基本:
focus(フォーカス時)/blur(フォーカスが外れたとき) - バブリング版:
focusin/focusout(親要素で一括管理) - フォーカス移動:
element.focus()/element.blur() - CSS との使い分け: スタイル変更は CSS :focus、バリデーションは JS blur
- tabindex: 0(Tab 順序に含む)/ -1(focus() のみ)
- アクセシビリティ: フォーカスリングを消さない、フォーカストラップ
フォーカスイベントはフォームの UX とアクセシビリティの両方に深く関わります。blur でのバリデーション、エラー時の focus 移動、モーダルのフォーカストラップは実務での必須テクニックです。