モーダルやハンバーガーメニューなど、JavaScriptで制御するUIは多くのサイトで使われています。
しかし一歩間違えると、特定のユーザーにとって「操作不能な状態」を作ってしまうことがあります。

「モーダルの中から出られなくなった」
「キーボード操作だと、今どこにいるか分からない」

これらはユーザーの混乱を招き、サイトからの離脱に繋がりかねません。
そこで今回は、JavaScriptで見落としがちなフォーカス制御のポイントをまとめました。

ページ内遷移(TOPへ戻る)のフォーカス制御

「ページトップへ戻る」ボタンや、目次リンクなどページ内リンクはよく使われます。

通常のaタグであれば、ブラウザが自動的にフォーカスを移動してくれるので問題ありません。
しかし、スムーススクロール実装などの理由でJavaScriptで処理する場合は注意が必要です。

例えば下記の実装の場合、event.preventDefault()を使用することで、ブラウザ本来のフォーカスの移動を止めてしまいます。

<a href="#about">このサイトについて</a>
<section id="about">・・・</section>
anchor.addEventListener('click', (e) => {
  e.preventDefault(); // デフォルトの挙動を止める
  const targetId = anchor.getAttribute('href');
  const targetElement = document.querySelector(targetId);

  window.scrollTo({
    top: targetElement.offsetTop,
    behavior: 'smooth'
  });
});

その結果、キーボードユーザーは「移動したつもりがアンカー元にフォーカスが残ったまま」になるので、現在地が分からなくなってしまいます。

改善例

これを避けるためには、スクロールと一緒にフォーカスを移す処理を追加します。

anchor.addEventListener('click', (e) => {
  e.preventDefault();
  const targetId = anchor.getAttribute('href');
  const targetElement = document.querySelector(targetId);

  window.scrollTo({
    top: targetElement.offsetTop,
    behavior: 'smooth'
  });

  targetElement.setAttribute('tabindex', '-1');  // フォーカスを当てられるようにする
  targetElement.focus({ preventScroll: true }); // focusによるジャンプを防ぐ
});

ポイントは以下の通りです。

  • スクロールだけでなくフォーカスも移動させる
  • tabindex="-1"を付与してフォーカス可能にする
  • preventScroll: trueでフォーカスによる再スクロールを防ぐ

モーダルのフォーカス制御

モーダルやポップアップも、フォーカス制御において注意すべきポイントの一つです。

単純にモーダルの表示/非表示を切り替えるだけでは不十分です。

モーダルの開閉時

モーダルが開いたら、モーダル内の最初の操作可能な要素へフォーカスを移動させます。

let triggerElement = null;

openButton.addEventListener('click', () => {
  triggerElement = document.activeElement; // 開く前の位置を保存
  openModal();

  // フォーカス可能な要素へカーソルを当てる
  const firstFocusable = modal.querySelector(
    'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
  );
  firstFocusable?.focus();
});

モーダルを閉じる時

モーダルを閉じた後に何も制御しないと、フォーカスが消えたり、ページの先頭に戻ってしまい、キーボードユーザーは迷子になってしまいます。

モーダルを開く時に、開く前の場所(モーダル表示ボタン)を変数に保持しておき、閉じるタイミングでそこへフォーカスを戻します。

closeButton.addEventListener('click', () => {
  closeModal();

  triggerElement?.focus(); // 元の位置へ戻す
});

これにより、モーダルを開く前の位置に戻れます。

モーダル外へのフォーカスを防ぐ

モーダル表示中に、モーダルの背景へのフォーカスが移動してしまうことがあります。
視覚的にはモーダルが前面にあっても、キーボード操作では裏側に移動してしまいます。

これを防ぐためにはinert属性を使います。

const mainContent = document.querySelector('#mainContent');

// モーダル表示中
mainContent.setAttribute('inert', '');

// モーダル非表示
mainContent.removeAttribute('inert');

inert を付与すると、その要素内にあるすべてのボタンやリンクは、

  • フォーカスが当たらない
  • スクリーンリーダーから除外

されます。

これによりモーダル表示中は、フォーカスがモーダル外へ漏れなくなります。

これはハンバーガーメニューやアコーディオンの表示/非表示にも応用できます。

まとめ

見た目では問題なく動いているように見えても、キーボードユーザーやスクリーンリーダー利用者にとっては「今どこにいるのかわからない」状態になっている可能性があります。

JavaScriptでUIに動きを加えるときは、「フォーカスの動きもセットで考える」ことを意識しましょう。