Back

ダイアログ表示中にページのスクロールを防ぐ方法

ダイアログ表示中にページのスクロールを防ぐ方法

モーダルダイアログを開く際は、ユーザーの注意をそのダイアログに集中させるべきであり、背後のページをスクロールさせるべきではありません。しかし、背景のスクロールを防ぐことは驚くほど困難であり、特にiOS Safariで信頼性の高いスクロールロック動作が必要な場合はなおさらです。

<dialog>要素とshowModal()を使用すると、インタラクションのブロックは自動的に処理されます。ブラウザはダイアログ外のすべてをinertとしてマークし、クリックやキーボードナビゲーションを防ぎます。しかし、スクロールの防止については別問題であり、プラットフォームは完全には解決していません。

この記事では、スクロールロックモーダルダイアログ実装の主なアプローチ、そのトレードオフ、そしてなぜすべての環境で完璧に機能する単一のソリューションが存在しないのかについて説明します。

重要なポイント

  • showModal()メソッドはインタラクションをブロックしますが、背景のスクロールは防ぎません
  • CSSのoverflow: hiddenはデスクトップでは機能しますが、iOS Safariでは失敗します
  • スクロール位置の復元を伴う固定ボディテクニックが、最も信頼性の高いクロスブラウザソリューションを提供します
  • 他の方法と併用してoverscroll-behavior: containを使用し、スクロールチェーンを防ぎます
  • アクセシビリティのために、常にmax-heightoverflow-y: autoでダイアログをスクロール可能に保ちます

showModal()が行うことと行わないこと

dialog.showModal()を呼び出すと、ブラウザは以下を実行します:

  • ダイアログをトップレイヤーに表示
  • ::backdrop疑似要素を作成
  • 背景コンテンツをinertにする(ポインターとキーボードのインタラクションをブロック)
  • フォーカスをダイアログ内にトラップ

行わないこと:すべてのブラウザとデバイスで背景のスクロールを確実に防ぐこと。一部のプラットフォーム、特にモバイルでは、ユーザーはタッチジェスチャーやスクロールホイールを使用してページをスクロールできてしまいます。

シンプルなCSSアプローチ:overflow: hidden

最も一般的なソリューションは、:has()セレクターを使用して開いているダイアログを検出します:

body:has(dialog[open]) {
  overflow: hidden;
}

これはほとんどのデスクトップブラウザで機能します。ダイアログが開いている間、ページはスクロール不可能になります。

**注意点:**このアプローチはiOS Safariでは信頼性がありません。タッチスクロールはbodyのoverflow: hiddenをバイパスすることが多く、ユーザーは背景をスクロールできてしまいます。モバイルSafariユーザーを含むオーディエンスがいる場合は、より堅牢なものが必要です。

クロスブラウザの信頼性を実現する固定ボディテクニック

iOS Safariのスクロールロックを含む、最も信頼性の高いクロスブラウザソリューションは、JavaScriptを使用してbodyを固定する方法です:

let scrollPosition = 0;

function lockScroll() {
  scrollPosition = window.scrollY;
  document.body.style.position = 'fixed';
  document.body.style.top = `-${scrollPosition}px`;
  document.body.style.width = '100%';
}

function unlockScroll() {
  document.body.style.position = '';
  document.body.style.top = '';
  document.body.style.width = '';
  window.scrollTo(0, scrollPosition);
}

ダイアログを開くときにlockScroll()を、閉じるときにunlockScroll()を呼び出します。

このテクニックはスクロール位置を保存し、bodyを固定してスクロールできないようにし、閉じるときにすべてを復元します。bodyが文字通り動けないため、iOSでのタッチスクロールにも対応します。

**トレードオフ:**スクロールバーが消えると、レイアウトシフトが発生する可能性があります。ロック時にスクロールバーの幅と等しいpadding-rightを追加することで補正できます。

スクロールチェーンを防ぐCSS overscroll-behavior

overscroll-behaviorプロパティは、スクロールチェーンを防ぎます。これは、ある要素内でスクロールが境界に達した後、親要素がスクロールしてしまう現象です。

dialog {
  overscroll-behavior: contain;
}

dialog::backdrop {
  overscroll-behavior: contain;
}

最近のChromiumバージョン(2025年後半以降)では、この動作が改善され、スクロール不可能なコンテナでも機能するようになりました。これはChromeでのHTMLダイアログのスクロールロックに役立ちますが、他のブラウザはまだ完全には追いついていません。

**重要:**CSSのoverscroll-behaviorモーダルソリューションは、スクロールチェーンを防ぐものであり、スクロール自体を防ぐものではありません。ユーザーがbackdropやbodyで直接スクロールする場合、overscroll-behaviorでは止められません。これは他のテクニックと併用し、置き換えとしては使用しないでください。

知っておくべきiOS Safariの特性

iOS Safariは、モーダル実装における背景スクロールの防止において独特の課題を提示します:

  • bodyのoverflow: hiddenはタッチスクロールを確実にブロックしません
  • ラバーバンドのオーバースクロール効果が背景の動きを引き起こす可能性があります
  • 仮想キーボードの表示が予期しないスクロール動作を引き起こす可能性があります

固定ボディテクニックが最も信頼性の高いソリューションです。一部の開発者は、backdrop要素(ダイアログ自体ではなく)にtouch-action: noneを追加しますが、これを広範囲に適用すると、ダイアログ内のスクロールに干渉する可能性があります。

ダイアログをスクロール可能に保つ

重要な詳細の1つ:ダイアログのコンテンツがビューポートの高さを超える場合、ダイアログ自体がスクロールできる必要があります。ダイアログに適切なmax-heightoverflow-y: autoを設定します:

dialog {
  max-height: 90vh;
  overflow-y: auto;
}

すべてのスクロールをブロックすると、アクセシビリティの問題が発生します。ユーザーはすべてのダイアログコンテンツにアクセスできる必要があります。

Popover APIについての注記

Popover APIは軽量なオーバーレイ用に存在しますが、モーダルダイアログの代替として直接使用できるものではありません。Popoverは背景のインタラクションを同じ方法でブロックせず、スクロールロック動作も異なります。スクロールロックが必要な真のモーダル体験には、<dialog>showModal()を使用してください。

アプローチの選択

デスクトップ専用アプリケーションの場合、overflow: hiddenを使用したCSS :has()アプローチで十分なことが多いです。クロスブラウザの信頼性、特にiOS Safariの場合は、スクロール位置の復元を伴う固定ボディテクニックを使用してください。サポートしているブラウザでスクロールチェーンを防ぐために、overscroll-behavior: containを上に重ねます。

まとめ

すべての環境で完璧に機能するソリューションはありません。固定ボディテクニックは、特にiOS Safariにおいて、最も信頼性の高いクロスブラウザスクロールロックを提供します。Chromiumブラウザでのスクロールチェーンに対する追加の保護として、overscroll-behavior: containと組み合わせてください。実機、特にiOS Safariでテストし、一部のエッジケースでは妥協が必要になる可能性があることを受け入れてください。

よくある質問

iOS Safariは、デスクトップブラウザとは異なる方法でタッチスクロールを処理します。ブラウザのタッチイベントシステムは、body要素のoverflow hiddenをバイパスできるため、プロパティが設定されていても背景がスクロールしてしまいます。固定ボディテクニックが機能するのは、bodyを物理的に画面外に配置し、単に非表示にするのではなく、スクロールを不可能にするためです。

はい、引き起こす可能性があります。スクロールバーが消えると、コンテンツがそのスペースを埋めるためにシフトする可能性があります。これを防ぐには、スクロールバーの幅を計算し、スクロールをロックする際にbodyに同等のpadding-rightを追加します。ロック解除時にはパディングを削除します。これにより、モーダルのインタラクション全体で一貫したレイアウトが維持されます。

Popover APIは異なる目的を果たします。背景のインタラクションをブロックしたり、同じスクロールロック機能を提供したりすることなく、軽量なオーバーレイを作成します。ユーザーが続行する前にダイアログと対話する必要がある真のモーダル体験には、適切なフォーカストラップとinert動作のために、showModalを使用したdialog要素を使用してください。

ダイアログにoverscroll-behavior containを設定して、背景へのスクロールチェーンを防ぎます。ダイアログにmax-heightとoverflow-y autoを設定して、内部コンテンツが適切にスクロールするようにします。ダイアログ全体にtouch-action noneを適用することは避けてください。これはモーダルコンテンツエリア内の正当なスクロールをブロックします。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay