メインコンテンツまでスキップ
  • Created:
  • Updated:
  • Author:
    Takeshi Takatsudo

オーバースクロールの挙動

問題

ネストされたスクロール可能領域 — モーダル、サイドバー、ドロップダウン、チャットパネル — の端までスクロールすると、ブラウザはスクロールイベントを最も近いスクロール可能な祖先に「チェーン」します。背景のページがオーバーレイの下で突然スクロールし始めます。これはスクロールチェーンと呼ばれ、ウェブアプリケーションで最もよく見られるUXバグの1つです。

overscroll-behavior が登場する前は、これを防ぐためにJavaScriptのスクロールロックハックが必要でした:wheelイベントのリスニング、スクロール位置の計算、適切なタイミングでの preventDefault() 呼び出しです。これらのソリューションは脆弱で、カクつきを引き起こし、タッチデバイスのスクロールを完全に壊すことが多かったです。モバイルでは、プルトゥリフレッシュのようなオーバースクロールエフェクトがカスタムスクロール領域内で予期せずトリガーされることもありました。

解決方法

overscroll-behavior プロパティは、スクロールコンテナが境界に達した時の動作を1行のCSSで制御します。

  • auto(デフォルト):通常の動作 — 親へのスクロールチェーンとネイティブオーバースクロールエフェクト(バウンス、プルトゥリフレッシュ)が有効です。
  • contain:親へのスクロールチェーンを防止しますが、ネイティブオーバースクロールエフェクト(iOS/macOSのラバーバンドバウンスなど)は要素自体の中では依然として適用されます。
  • none:スクロールチェーンとすべてのネイティブオーバースクロールエフェクトの両方を防止します。スクロールは単に停止します。

overscroll-behavior-xoverscroll-behavior-y で各軸を個別に制御することもできます。

基本原則

auto(デフォルト)

デフォルト値です。要素がスクロール境界に達すると親にチェーンします。メインページコンテンツでは通常問題ありませんが、オーバーレイ、モーダル、サイドバーでは問題を引き起こします。

contain

最もよく必要とされる値です。スクロールチェーンを防止し、スクロールが要素内に閉じ込められます。モーダル、サイドバー、チャットパネル、ドロップダウンメニューなど、背景をスクロールさせたくない独立したスクロール可能領域に使用しましょう。

none

contain よりさらに進んで、ラバーバンドバウンスやプルトゥリフレッシュなどのネイティブオーバースクロールエフェクトも抑制します。境界でスクロールが視覚的フィードバックなしで完全に停止してほしい場合に使用します。埋め込みアプリライクなインターフェースに便利です。

スクロールチェーンの問題(修正なし)
overscroll-behavior: contain でスクロールチェーンを修正
contain vs none — 並べて比較
スクロール制御付きチャットパネル
スクロール制御付きサイドバーナビゲーション

コード例

モーダルでのスクロールチェーン防止

.modal-body {
max-height: 80vh;
overflow-y: auto;
overscroll-behavior-y: contain;
}

この1行で、モーダルコンテンツの端までスクロールした際に背景ページがスクロールするのを防止します。

ブラウザの戻るナビゲーションの誤発動防止

.horizontal-scroller {
overflow-x: auto;
overscroll-behavior-x: contain;
}

一部のブラウザでは、水平オーバースクロールジェスチャーがブラウザの戻る/進むナビゲーションをトリガーします。水平スクロール領域に overscroll-behavior-x: contain を設定することで、この誤ナビゲーションを防止します。

コンテナを制御したアプリシェル

.app-shell {
display: grid;
grid-template-columns: 250px 1fr 300px;
height: 100vh;
}

.sidebar-nav {
overflow-y: auto;
overscroll-behavior: contain;
}

.main-content {
overflow-y: auto;
}

.detail-panel {
overflow-y: auto;
overscroll-behavior: contain;
}

マルチパネルレイアウトでは、各セカンダリスクロールパネルに overscroll-behavior: contain を適用し、メインコンテンツ領域へのスクロールチェーンを防止しましょう。

プルトゥリフレッシュの無効化

body {
overscroll-behavior-y: none;
}

モバイルブラウザでは、ページ上部で下に引くとリフレッシュがトリガーされます。body に overscroll-behavior-y: none を設定することでこの動作を無効にできます。独自のリフレッシュメカニズムを持つウェブアプリに便利です。

AIがよくやるミス

  • overscroll-behavior を一切提案しない:単一のCSSプロパティで解決できる問題にJavaScriptのスクロールロックライブラリや event.preventDefault() ハックをデフォルトで使用します。
  • 間違った要素に適用するoverscroll-behaviorスクロールを持つ要素(overflow: auto または overflow: scroll)に設定する必要がありますが、親やラッパーに設定してしまいます。
  • contain ではなく常に none を使用するnone はすべてのオーバースクロールフィードバックを抑制するため、不自然に感じることがあります。バウンスエフェクトを特に抑制する必要がない限り、contain を優先しましょう。
  • 軸固有のバリアントを忘れる:水平スクローラーの overscroll-behavior-x: contain のように1軸のみ制御が必要な場合に overscroll-behavior: contain を使用します。
  • overflow と組み合わせないoverscroll-behavior はスクロールコンテナにのみ効果を発揮します。要素に overflow: auto または overflow: scroll がない場合、このプロパティは効果がありません。

使い分け

  • モーダルとダイアログ:モーダル内でスクロールした際の背景ページスクロールを防止します。
  • サイドバーとナビゲーションパネル:サイドバーのスクロールをメインコンテンツから独立させます。
  • チャット・メッセージングパネル:ユーザーがメッセージ履歴をスクロールする際のページスクロールを防止します。
  • ドロップダウンメニュー:長いドロップダウンが背後のページをスクロールさせないようにします。
  • 水平スクロール領域overscroll-behavior-x: contain でブラウザの戻る/進むナビゲーションの誤発動を防止します。
  • モバイルウェブアプリ:アプリが独自のリフレッシュロジックを処理する場合、body に overscroll-behavior-y: none でプルトゥリフレッシュを無効にします。

参考リンク