HTMLでモーダルUIを作るときに気をつけたいこと

204
441
100

ダイアログやハンバーガーメニューといったユーザーインタフェース(UI)は、多くのウェブサイトで利用されており頻繁に見かけます。どこでも見かけることから「簡単に作成できる」と思われがちですが、意外と実装が難しいUIです。たとえば、エンジニアでなくとも、以下のような現象に気付いたことはないでしょうか?

  • ダイアログを表示中に、裏側のコンテンツがスクロールできてしまった
  • ダイアログを表示中に、Tabキーでキーボード操作を行うと裏側を操作できてしまった

▼裏側がスクロールできてしまう例

裏側がスクロールできてしまう例

▼裏側がキーボード操作できてしまう例

裏側がキーボード操作できてしまう例

これらを解決するためには、手軽な正攻法はなく、複雑なJavaScriptの制御が必要になります。本記事では、ダイアログやハンバーガーメニュー等のモーダル系のUIに存在する気付きづらい問題点と、解決方法を紹介します。ダイアログとハンバーガーメニューはそれぞれ役割の異なるUIですが、画面全域を覆うUIという意味において同種の問題が発生するので、本記事ではあわせて説明します。

よくありがちなHTMLの実装を紹介

問題点を示すために、シンプルなHTMLの作例を用意しました。ダイアログとハンバーガーメニューのデモです。それぞれのボタンをクリックすると画面全域を覆うUIが出現し、[閉じる]ボタンをクリックすることで閉じられます。

▼モーダルダイアログの表示

表示を切り替えるだけのシンプルなモーダルダイアログ

▼ハンバーガーメニューの表示

表示を切り替えるだけのシンプルなハンバーガーメニュー

CSSとJSの制御として、要素に.is-showというCSSクラスを付与することで表示させています。

▼モーダルダイアログのコード例

// DOM要素の参照を取得
const modalOpenButton = document.querySelector('#js-modal-button');
const modalCloseButton = document.querySelector('#js-modal-close');
const modalOverlay = document.querySelector('#js-modal-overlay');
const modalContent = document.querySelector('#js-modal');

// 開くボタンがクリックされたらモーダルを開く
modalOpenButton.addEventListener('click', () => {
  modalContent.classList.add('is-show');
  document.body.classList.add('is-scrollLock');
});
// 閉じるボタンまたはモーダルの背景がクリックされたらモーダルを閉じる
const closableElement = [modalCloseButton, modalOverlay];
closableElement.forEach((element) => {
  element.addEventListener('click', () => {
    modalContent.classList.remove('is-show');
    document.body.classList.remove('is-scrollLock');
  });
});

モーダルの表示中は<body>要素にスタイルoverflow: hiddenを設定し、マウスホイールやタッチ操作によるスクロールを無効化しています。

一見、問題なく動作しているように見えますが、以下の2つの課題があります。先ほどの作例をブラウザで開きながら問題点を確認していきましょう。

  • 課題1: iOS Safariで裏側がスクロールされる現象
  • 課題2: 裏側にキーボードフォーカスされる現象

課題1: iOS Safariで裏側がスクロールされる現象

スタイルoverflow: hiddenでスクロールを固定していても、iOS Safariでは以下のような特定のタイミングで裏側のコンテンツがスクロールできてしまいます

  • 画面下部のタブバーの表示が切り替わるタイミング
  • 最前面のコンテンツを上下どちらかにスクロールしきった後に、もう一度スクロールしたタイミング

▼モーダルダイアログの裏側がスクロールされる様子

モーダルダイアログの裏側がスクロールされる例

▼ハンバーガーメニューの裏側がスクロールされる様子

ハンバーガーメニューの裏側がスクロールされる例

この挙動は、モーダルダイアログ内にスクロール可能なコンテンツが存在する場合に弊害があります。ページ全体のスクロール挙動に影響をうけ、モーダルダイアログ内でのスクロールができなくなります。モーダルダイアログ内にスクロール可能なコンテンツがない場合は問題になりませんが、UIの制約が生まれてしまうので対策を検討したいところです。

この課題を対策した作例

この課題を対策するには、以下の方針が考えられます。

  • スクロールをさせたくない要素にはEventpreventDefault()stopPropagation()メソッドでスクロール挙動を抑制
  • スクロール対象の要素を上下どちらかにスクロールしきった時に、スクロール量を微調整

この方針で対策した作例は以下の通りです。

▼モーダルダイアログの裏側を固定する例

モーダルダイアログの裏側を固定する例

▼メニューの裏側を固定する例

メニューの裏側をする例

具体的なJavaScriptの実装は次のリンク先から参照ください。

課題2: 裏側にキーボードフォーカスされる現象

キーボード操作を行うと、モーダルの裏側のコンテンツにフォーカスがあたってしまう問題があります。モーダルダイアログの表示中は背面が操作できないような表示になりますが、Tabキーでフォーカスを移動できています。モーダルダイアログの表示中に裏側のボタンや入力欄の操作ができるため、意図しない動作の起きるリスクが考えられます。

メニューの裏側がキーボードフォーカスされる例

もう1つ関連した問題があります。モーダルダイアログの実装においては、z-indexの重なり順の対策もかねて<body>要素の末尾に表示用の要素が置くことあります。その実装をすると、モーダルダイアログの表示直後にフォーカスがすぐに当たらないという現象も発生します。

モーダルを開くボタンとダイアログのDOM要素の間に、フォーカス可能なDOM要素が存在するため、フォーカスがモーダルダイアログでない場所にあたってしまいます。

間にある要素がフォーカスされるコード例

この課題を対策した作例

これを対策するにはkeydownイベントのケアが必要となります。作例とコードを示すので、詳細を知りたい方はぜひ参考ください。

メニューのキーボードフォーカスを制御する例

対応方針

  • スクリーンリーダー向けのWAI-ARIAウェイ・アリア対応
  • keydownイベントでのフォーカスの制御(該当コード

コラム: キーボードフォーカスの制御をinert属性で実装する

キーボードフォーカスの制御は、inert属性を利用して実装することもできます。inert属性はあらゆるユーザー操作を無効化するHTMLのグローバル属性で、キーボードフォーカスやスクリーンリーダーによる操作を無効化できます。

紹介した作例では、スクリーンリーダーとキーボードフォーカスの問題を個別に対策しましたが、inert属性を利用することでまとめて対策できます。

inert属性は、2023年4月12日にFirefox 112がリリースされたことにより、主要なブラウザで利用可能となりました。inert属性を利用したキーボードフォーカスの制御を次の記事で紹介していますので、詳しく知りたい方は参考にしてみてください。

コラム: <dialog>要素とoverscroll-behaviorプロパティでモーダルダイアログを実装する

キーボードフォーカスやスクリーンリーダーをケアする別の方法として、<dialog>要素でモーダルダイアログを実装する方法があります。<dialog>要素はopen()close()など、ダイアログの開閉に必要なJavaScriptのメソッドが備わっています。

以下のデモは<dialog>要素を利用した実装例です。

dialog要素を使用したモーダルダイアログ

<dialog>要素はブラウザ標準の仕様です。<dialog>要素で実装すると、キーボードフォーカスやスクリーンリーダーの挙動も手軽にケアできます。

<dialog>要素は主要なブラウザの最新版であれば利用可能です。ただし、2022年3月リリースのFirefox 98以上やSafari 15.4以上で利用可能のため、それ未満のバージョンのブラウザを考慮する場合は別の方法を検討する必要があります。

注意点として、<dialog>要素はキーボードフォーカスやスクリーンリーダーの対策に利用できますが、前述したiOS Safariのスクロール挙動の対策には効果がありません。別の対策として、CSSのoverscroll-behaviorプロパティを利用することで、<dialog>要素でもスクロール挙動の対策ができます。

overscroll-behaviorプロパティは、主要なブラウザの最新版であれば利用可能です。ただし、2022年9月リリースのSafari 16以上で利用可能なため、それ未満のバージョンのブラウザを考慮する場合は別の方法を検討する必要があります。

<dialog>要素とoverscroll-behaviorプロパティを利用したダイアログの実装は、次の記事で紹介していますので、詳しく知りたい方は参考にしてみてください。

まとめ

モーダル系のUIでの「裏側のコンテンツ」に関する注意点を本記事で紹介しました。「スクロールの制御」「フォーカスの制御」の両方をケアしないと良いモーダルUIの挙動にはなりません。「ささいなことだから、対策しなくてもいいのでは・・・」となりがちな挙動かもしれませんが、ユーザビリティーやウェブアクセシビリティの観点で改善できれば理想的です。本記事がこれらの問題をケアするために参考となれば幸いです。

※この記事が公開されたのは1年前ですが、1年前の2023年4月に内容をメンテナンスしています。

古舘 和志

フロントエンドエンジニア。ウェブデザイナーのようなHTMLコーダーからフロントエンドエンジニアに転身。現在は岩手からリモートで勤務中。

この担当の記事一覧