基礎

JavaScriptのフォーカスイベント入門|focus・blur・focusin・focusoutの使い方

JavaScriptのフォーカスイベントは、ユーザーがフォーム要素やリンクなどにカーソルを合わせたり、要素から離れたりしたときに発火するイベントです。入力フォームのバリデーションやUIのハイライト表示など、Webアプリケーションのインタラクション設計において欠かせない仕組みです。

フォーカスイベントには主に4種類あり、それぞれ発火タイミングやバブリングの有無が異なります。この記事では、各イベントの違いを実際のコードで確認しながら、実践的な使い方を解説します。

基本的な使い方

フォーカスイベントの中で最も基本的なのが focusblur です。focus は要素にフォーカスが当たったとき、blur はフォーカスが外れたときに発火します。

JavaScript
const input = document.querySelector('#username');

// フォーカスが当たったとき
input.addEventListener('focus', function() {
  console.log('入力欄にフォーカスが当たりました');
  this.style.borderColor = '#4a90d9';
  this.style.boxShadow = '0 0 5px rgba(74, 144, 217, 0.5)';
});

// フォーカスが外れたとき
input.addEventListener('blur', function() {
  console.log('入力欄からフォーカスが外れました');
  this.style.borderColor = '#ccc';
  this.style.boxShadow = 'none';
});
実行結果
// 入力欄をクリックしたとき
入力欄にフォーカスが当たりました

// 入力欄の外をクリックしたとき
入力欄からフォーカスが外れました

上記の例では、テキスト入力欄にフォーカスが当たると枠線の色が青に変わり、フォーカスが外れると元に戻ります。CSSの :focus 擬似クラスでも同様の見た目は実現できますが、JavaScriptを使うことでバリデーション処理やAPI呼び出しなど、より複雑なロジックを組み込めます。

focusin と focusout(バブリング対応)

focusblur はバブリングしません。つまり、親要素でイベントをまとめてキャッチすることができません。この問題を解決するのが focusinfocusout です。

JavaScript
const form = document.querySelector('#myForm');

// focusin はバブリングするので親要素で捕捉できる
form.addEventListener('focusin', function(e) {
  console.log('フォーカスされた要素:', e.target.name);
  e.target.classList.add('active-input');
});

form.addEventListener('focusout', function(e) {
  console.log('フォーカスが外れた要素:', e.target.name);
  e.target.classList.remove('active-input');
});
実行結果
// 名前入力欄をクリック
フォーカスされた要素: username

// メール入力欄に移動
フォーカスが外れた要素: username
フォーカスされた要素: email

フォーム内に複数の入力欄がある場合、focusin / focusout を親要素(フォーム)に1つだけ設定すれば、すべての子要素のフォーカス変化を検知できます。これはイベントデリゲーションと呼ばれるパターンで、パフォーマンスの面でも有利です。

実践例:入力バリデーション

フォーカスイベントの最も一般的な用途は、フォームのリアルタイムバリデーションです。ユーザーが入力欄から離れたタイミングで値をチェックし、エラーメッセージを表示します。

JavaScript
const emailInput = document.querySelector('#email');
const errorMsg = document.querySelector('#email-error');

emailInput.addEventListener('blur', function() {
  const value = this.value.trim();

  if (value === '') {
    errorMsg.textContent = 'メールアドレスを入力してください';
    this.classList.add('input-error');
  } else if (!value.includes('@')) {
    errorMsg.textContent = '正しいメールアドレスを入力してください';
    this.classList.add('input-error');
  } else {
    errorMsg.textContent = '';
    this.classList.remove('input-error');
    this.classList.add('input-valid');
  }
});

emailInput.addEventListener('focus', function() {
  // フォーカス時にエラー表示をリセット
  this.classList.remove('input-error', 'input-valid');
});

この例では、blur イベントで入力値をチェックし、空欄や不正な形式の場合はエラーメッセージを表示します。focus イベントでは、再入力時にエラー表示をリセットしています。ユーザーにとって自然なタイミングでフィードバックを返せるのがフォーカスイベントの強みです。

activeElement でフォーカス中の要素を取得

document.activeElement を使うと、現在フォーカスされている要素をいつでも取得できます。デバッグ時やアクセシビリティのテストに便利です。

tabindex に注意

divspan などの非フォーム要素は、デフォルトではフォーカスを受け取れません。これらの要素でフォーカスイベントを使いたい場合は、tabindex="0" 属性を付与する必要があります。ただし、意味のないフォーカス可能要素を増やすとアクセシビリティに悪影響を与えることがあるため注意してください。

まとめ

  • focus / blur は要素にフォーカスが当たった・外れたときに発火する基本イベント
  • focusin / focusout はバブリングするため、親要素でまとめてイベントをキャッチできる
  • フォームバリデーションでは blur で値チェック、focus でリセットするパターンが定番
  • document.activeElement で現在フォーカス中の要素を取得できる
  • 非フォーム要素には tabindex を設定しないとフォーカスイベントが発火しない