【JavaScript】フォーカスイベントの使い方|focus・blur・focusin・focusout・フォームバリデーション・アクセシビリティまで解説

フォームの入力欄がフォーカスされたときにヘルプテキストを表示する、フォーカスが外れたときにバリデーションを実行する、モーダル内でフォーカスを閉じ込めるなど、フォーカスイベントはフォーム操作やアクセシビリティの実装に欠かせません。

この記事でわかること
・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 は inputtextareaselectbuttona などフォーカス可能な要素で使えます。

focusin / focusout(バブリングする版)

focusblur はバブリングしないため、親要素で子要素のフォーカスをまとめて検知できません。バブリングする 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 でフォーカス可能にする

divspan などのフォーカス不可能な要素に 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 のダイアログパターンに基づくベストプラクティスです。

関連記事

よくある質問

Qfocus と focusin の違いは何ですか?
Afocus はバブリングしないため、親要素でリスナーを設定しても子要素のフォーカスは検知できません。focusin はバブリングするため、親要素で子要素のフォーカスをまとめてキャッチできます。フォーム全体のフォーカス管理には focusin が便利です。
Qdiv や span にフォーカスを当てるにはどうすればよいですか?
Atabindex="0" を設定するとフォーカス可能になります。Tab キーでのフォーカスが不要で JavaScript の focus() でのみフォーカスしたい場合は tabindex="-1" を使います。
QCSS の :focus と :focus-visible の違いは?
A:focus はすべてのフォーカスに適用されます。:focus-visible はキーボード操作でフォーカスした場合のみ適用され、マウスクリックでは適用されません。ボタンのフォーカスリングをキーボード操作時のみ表示したい場合に使います。
Qblur イベントで relatedTarget が null になります。
ArelatedTarget はフォーカスの移動先要素を返しますが、フォーカスがページ外(ブラウザの 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 移動、モーダルのフォーカストラップは実務での必須テクニックです。