【JS応用】フォーカス: focus/blur

フォーカス管理の深淵:WebアプリケーションにおけるアクセシビリティとUXの最適化

Webアプリケーションの品質を左右する要素の中で、キーボード操作やスクリーンリーダーの挙動に直結する「フォーカス管理」は、最も軽視されがちでありながら、最も重要なコンポーネントの一つです。単に要素をクリック可能にするだけでなく、ユーザーが現在どこにいて、何ができるのかを明確に示すことは、インクルーシブなデザインの根幹を成します。本記事では、フォーカス制御の技術的側面から、堅牢な実装手法、そして実務で遭遇する複雑なエッジケースまでを網羅的に解説します。

フォーカスの基本メカニズムとイベントライフサイクル

ブラウザにおけるフォーカスとは、ユーザーからの入力(キーボード、ポインターデバイスなど)を現在受け取っている要素を指します。フォーカスが移動する際、ブラウザは「blur」と「focus」という2つの主要なイベントを発火させます。

フォーカスが要素Aから要素Bへ移動する場合の順序は以下の通りです。
1. 要素Aに対して blur イベントが発生
2. 要素Bに対して focus イベントが発生

ここで重要なのは、これらのイベントが同期的に処理される点です。また、focusin と focusout イベントはバブリングするため、親要素でフォーカスの変化を検知する場合にはこれらのイベントが適しています。

実装において注意すべきは「フォーカス可能な要素」の定義です。デフォルトでタブフォーカスが当たるのは、aタグ(href属性あり)、button、input、textarea、select、そして tabindex 属性が0以上の要素です。tabindex=”-1″ を指定すると、プログラムによるフォーカスは可能ですが、キーボードのタブ操作による遷移からは除外されます。これはモーダルウィンドウやドロップダウンの制御において極めて重要な役割を果たします。

アクセシビリティの黄金律:フォーカス・インジケータ

フォーカス・インジケータ(フォーカスリング)をCSSで `outline: none;` として消し去る実装は、Webアクセシビリティにおける最大の罪です。キーボードユーザーはフォーカスリングがないと、自分がページのどこを操作しているのかを認識できず、完全に迷子になります。

現代のフロントエンド開発では、フォーカスリングを消すのではなく、デザインシステムに合わせてカスタマイズするのが正解です。以下は、堅牢かつ視認性の高いフォーカススタイルのサンプルです。


/* フォーカスリングを美しくカスタマイズする */
:focus-visible {
  outline: 3px solid #2563eb;
  outline-offset: 2px;
  border-radius: 4px;
}

/* マウスユーザーには不要なインジケータを隠す工夫 */
:focus:not(:focus-visible) {
  outline: none;
}

`focus-visible` 擬似クラスは、ブラウザが「ユーザーがキーボードで操作している」と判断したときにのみスタイルを適用するため、マウスユーザーにはクリーンな見た目を、キーボードユーザーには明確なガイドを提供できます。

プログラムによるフォーカス制御の実装パターン

シングルページアプリケーション(SPA)において、ページ遷移やモーダルの開閉時にフォーカスを適切に移動させることは、ユーザー体験を劇的に向上させます。

特にモーダルウィンドウの実装では「フォーカストラップ」という技術が必要です。これは、Tabキーを押した際にフォーカスがモーダルの外(背景のコンテンツ)に漏れないようにする仕組みです。


// モーダルオープン時のフォーカス制御ロジック
function trapFocus(modalElement) {
  const focusableElements = modalElement.querySelectorAll(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );
  const firstElement = focusableElements[0];
  const lastElement = focusableElements[focusableElements.length - 1];

  modalElement.addEventListener('keydown', (e) => {
    if (e.key !== 'Tab') return;

    if (e.shiftKey) { // Shift + Tab
      if (document.activeElement === firstElement) {
        e.preventDefault();
        lastElement.focus();
      }
    } else { // Tab
      if (document.activeElement === lastElement) {
        e.preventDefault();
        firstElement.focus();
      }
    }
  });
}

このコードは、モーダル内の最初と最後の要素を特定し、フォーカスが境界線を超えようとした瞬間に反対側へ強制的に移動させることで、閉じたループを作成します。

実務における高度なフォーカス管理戦略

実務でフロントエンドエンジニアが直面する最も厄介な問題は、動的にDOMが書き換わる際のフォーカス消失です。例えば、ReactやVueなどのフレームワークで、ボタンを押した瞬間にそのボタン自体が削除されるようなレンダリングを行うと、ブラウザのフォーカスは body 要素へ戻ってしまい、キーボード操作の文脈がリセットされます。

これを防ぐためのベストプラクティスは以下の通りです。

1. 状態変化の直前に `document.activeElement` を保存し、DOM更新後に `focus()` を呼び出す。
2. アラートや通知を表示する場合、その要素に `role=”alert”` を付与し、かつフォーカスを移動させるべきか、あるいはスクリーンリーダーの読み上げに任せるかを慎重に判断する。
3. ページのトップに「メインコンテンツへスキップ」リンクを必ず配置する。これは全盲のユーザーがヘッダーのナビゲーションを毎回読み飛ばさずに済むための必須要件です。

また、`element.focus({ preventScroll: true })` オプションの活用も重要です。フォーカスを当てた際にブラウザが自動的にスクロールしてしまう挙動を抑制できるため、モーダルやドロップダウン内でフォーカスを移動させる際に意図しない画面のガタつきを防ぐことができます。

モダンブラウザと今後の展望

現在、Web標準では `inert` 属性の普及が進んでいます。`inert` を要素に付与すると、その要素とその子孫要素は、フォーカスを受け取らなくなり、スクリーンリーダーからも無視されるようになります。


// モーダルを開くときに背景を非活性化する
const mainContent = document.querySelector('#main-content');
mainContent.inert = true;

// モーダルを閉じるときに復元する
mainContent.inert = false;

これまで手動で aria-hidden を付与したり、tabindex を -1 に変更したりしていた複雑な処理が、`inert` 属性一つで完結します。これにより、JavaScriptによるDOM操作のオーバーヘッドを減らしつつ、より確実なフォーカス管理が可能になりました。

まとめ:ユーザーの視点に立つフォーカス設計

フォーカス管理は、単なる「技術的要件」ではありません。それはユーザーに対する「礼儀」です。ユーザーが今どこにいて、次に何ができるのかを明確に伝えることこそが、優れたWebアプリケーションの条件です。

本記事で解説したフォーカス・インジケータの最適化、フォーカストラップの実装、そして `inert` 属性のような最新のAPI活用は、いずれも必須のスキルセットです。コードを書く際、常に「もし自分がマウスを使えなかったら、このページをスムーズに操作できるか?」と自問自答してください。その問いかけこそが、プロフェッショナルなフロントエンドエンジニアとしての品質を担保する唯一の道です。

技術は常に進化しますが、ユーザーが快適に操作したいという根本的な欲求は変わりません。フォーカス管理という細部にこそ、エンジニアとしてのこだわりと、ユーザーへの敬意を込めて実装を続けていきましょう。

コメント

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