JavaScript | DOM 操作:イベント発展 – フォーカスイベント

JavaScript
スポンサーリンク

フォーカスイベントとは何か

フォーカスイベントは「どの要素がキーボード操作の対象になったか/外れたか」を知らせる合図です。代表は focus(入る)、blur(出る)、そしてバブリングする仲間の focusin(入る)/focusout(出る)。ここが重要です:focus/blur はバブリングしないので“親でまとめて受ける”には向きません。親で一括処理したいなら focusin/focusout を使います。


基本の使い方(focus / blur と focusin / focusout)

個別要素で扱う(focus / blur)

<input id="name" placeholder="名前">
<script>
  const name = document.getElementById("name");
  name.addEventListener("focus", () => {
    console.log("名前にフォーカスが入りました");
    name.classList.add("highlight");
  });
  name.addEventListener("blur", () => {
    console.log("フォーカスが外れました");
    name.classList.remove("highlight");
  });
</script>
HTML

ここが重要です:focus/blur は“その要素だけ”の入出を見たいときに使います。スタイルの切り替えや軽い検証に向いています。

親でまとめて扱う(focusin / focusout)

<form id="f">
  <input name="email" type="email" placeholder="メール">
  <input name="age" type="number" placeholder="年齢">
</form>
<script>
  f.addEventListener("focusin", (e) => {
    e.target.classList.add("focus");
  });
  f.addEventListener("focusout", (e) => {
    e.target.classList.remove("focus");
  });
</script>
HTML

ここが重要です:focusin/focusout はバブリングするため、親に1つだけリスナーを付けて“どの子に入ったか”を e.target で判定できます。動的に追加した入力にも自動対応でき、管理が楽になります。


キーボード操作と tabindex(どこで受けるかを設計する)

非入力要素をフォーカス可能にする

<div id="pane" tabindex="0" style="border:1px solid #ccc">ここで矢印キーを受ける</div>
<script>
  pane.addEventListener("focus", () => pane.classList.add("active"));
  pane.addEventListener("blur",  () => pane.classList.remove("active"));
  pane.addEventListener("keydown", (e) => {
    if (e.key.startsWith("Arrow")) console.log("矢印:", e.key);
  });
</script>
HTML

ここが重要です:tabindex=”0″ を付けると、div なども“タブ移動の対象”になります。キーボードで操作したい領域は、フォーカス可能にしてそこでイベントを受ける設計にします。

タブ順に入れないがプログラムでフォーカスしたい(tabindex=-1)

<button id="open">開く</button>
<div id="dialog" tabindex="-1" hidden>ダイアログ</div>
<script>
  open.addEventListener("click", () => {
    dialog.hidden = false;
    dialog.focus(); // ユーザーのタブ順に混ざらず、APIでだけフォーカス
  });
</script>
HTML

ここが重要です:tabindex=”-1″ は“タブ移動に参加しないが focus() はできる”。モーダルの初期フォーカスや、エラーメッセージへ一時的にフォーカスを当てるときに便利です。


検証と状態同期(blurで確定、focusで準備)

blur で確定検証、focus で補助表示

<input id="email" type="email" placeholder="example@example.com" required>
<div id="hint" hidden>会社のメールを推奨</div>
<script>
  email.addEventListener("focus", () => { hint.hidden = false; });
  email.addEventListener("blur",  () => {
    hint.hidden = true;
    if (!email.checkValidity()) {
      email.classList.add("error");
    } else {
      email.classList.remove("error");
    }
  });
</script>
HTML

ここが重要です:入力中は“補助表示”、フォーカスが外れたタイミングで“厳密な検証”。ライブ中に赤字連発はストレスなので、確定時の優しい設計にします。

フォーカス移動でボタンの有効/無効を同期

<form id="f">
  <input id="name" required>
  <button id="next" disabled>次へ</button>
</form>
<script>
  const sync = () => { next.disabled = !f.checkValidity(); };
  f.addEventListener("focusin",  sync);
  f.addEventListener("focusout", sync);
  f.addEventListener("input",    sync); // 値の変化にも対応
</script>
HTML

ここが重要です:focus と input を組み合わせ、“その瞬間の妥当性”でボタン状態を常に同期します。単一ロジックで制御するとズレが起きません。


モーダルとフォーカストラップ(閉じるまで内側に留める)

モーダル内にフォーカスを閉じ込める

<div id="backdrop" hidden>
  <div id="modal" role="dialog" aria-modal="true">
    <button id="close">閉じる</button>
    <a href="#">リンク</a>
    <input placeholder="入力">
  </div>
</div>
<script>
  function openModal() {
    backdrop.hidden = false;
    modal.focus(); // 初期フォーカス
    document.addEventListener("focusin", trap, { capture: true });
  }
  function closeModal() {
    backdrop.hidden = true;
    document.removeEventListener("focusin", trap, { capture: true });
  }
  function trap(e) {
    if (!backdrop.hidden && !modal.contains(e.target)) {
      e.stopPropagation(); // 外へ出ようとするフォーカスを遮断
      modal.focus();
    }
  }
  close.addEventListener("click", closeModal);
</script>
HTML

ここが重要です:モーダルが開いている間は“外側へフォーカスが逃げない”設計が必要。focusin(キャプチャ)で外への移動を見張り、内側へ戻します。Esc キーで閉じるなどキーボード操作も合わせると直感的です。


e.relatedTarget で“移動元/移動先”を掴む

どこから来て、どこへ行ったか

<input id="a"><input id="b">
<script>
  a.addEventListener("blur", (e) => {
    console.log("a から出た。移動先:", e.relatedTarget); // 次にフォーカスされた要素
  });
  b.addEventListener("focus", (e) => {
    console.log("b に入った。移動元:", e.relatedTarget); // 直前にフォーカスされていた要素
  });
</script>
HTML

ここが重要です:FocusEvent の relatedTarget で“前後の相手”がわかります。連携 UI(片方からもう片方へ値移行など)を実装するときに便利です。環境によって null になる場合がある点は考慮しましょう。


よくある落とし穴と回避策

focus/blur を親で受けようとして動かない

focus/blur はバブリングしません。ここが重要です:親一括の処理は focusin/focusout を使う。委譲の基本は“バブルで受ける”ことです。

preventDefault でフォーカス移動を止めようとする

フォーカスイベントはキャンセル不可です。ここが重要です:止める設計ではなく“行き先を正しく誘導する”設計にする(tabindex、focus() の活用、フォーカストラップ)。

キーボードフォーカスを奪う UI

クリック中心の設計でフォーカスを無視すると、キーボードユーザーが操作できません。ここが重要です:button/a/input など“適切な要素”を使い、初期フォーカスやタブ順(tabindex)を意識します。

エラー表示のタイミングが早すぎる

入力途中に blur なしで厳密検証を連発すると体験が悪化。ここが重要です:input 中は穏やかな補助、blur で確定検証の二段構えにする。


実践例(フィールドハイライト、ステップフォーム、ヘルプ提示)

今いるフィールドをハイライト

<form id="f">
  <input placeholder="氏名">
  <input placeholder="メール">
</form>
<script>
  f.addEventListener("focusin", (e) => e.target.classList.add("active"));
  f.addEventListener("focusout", (e) => e.target.classList.remove("active"));
</script>
HTML

ステップフォーム:blur でステップ進行

<form id="step">
  <input name="email" type="email" required placeholder="メール">
  <div id="next" hidden>次のステップへ進めます</div>
</form>
<script>
  step.addEventListener("focusout", () => {
    if (step.checkValidity()) next.hidden = false;
  });
</script>
HTML

フォーカス時に補助を表示、外れたら隠す

<label>
  パスワード
  <input id="pw" type="password">
  <span id="rule" hidden>8文字以上・数字と英字を含む</span>
</label>
<script>
  pw.addEventListener("focus", () => rule.hidden = false);
  pw.addEventListener("blur",  () => rule.hidden = true);
</script>
HTML

まとめ

フォーカスイベントは「どの要素が操作対象か」を教えてくれる基盤です。個別は focus/blur、親でまとめるなら focusin/focusout。tabindex で“どこに届くか”を設計し、blur で確定検証、focus で補助表示の二段構えにする。モーダルではフォーカストラップで内側に留め、relatedTarget で前後関係を活用する。キャンセル不可であることを前提に、“正しく誘導する”設計へ徹すれば、初心者でも気持ちよく操作できるキーボードフレンドリーな UI を作れます。

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