【CSS】ページのスクロールを禁止する方法|overflow・overscroll-behavior・モーダル対応まで

【CSS】ページのスクロールを禁止する方法|overflow・overscroll-behavior・モーダル対応まで HTML/CSS

CSSでページのスクロールを禁止する方法を解説します。

単純な overflow: hidden だけでなく、軸ごとの制御overscroll-behavior によるスクロール連鎖の防止モーダル表示時の背景スクロール禁止iOS Safari での注意点まで、実務で必要になるパターンを網羅します。

スポンサーリンク

overflow: hidden でスクロールを禁止する

もっとも基本的な方法は、htmlbodyoverflow: hidden を指定することです。

html, body {
  overflow: hidden;
}

これだけでページ全体のスクロールが禁止されます。

overflow プロパティの値は以下の4種類です。

動作 スクロールバー
visible はみ出た部分をそのまま表示(デフォルト) なし
hidden はみ出た部分を非表示にする なし
scroll はみ出た部分をスクロール可能にする 常に表示
auto はみ出た場合のみスクロール可能にする 必要時のみ表示

hidden を指定すると、コンテンツがはみ出てもスクロールバーが表示されず、はみ出た部分は見えなくなります。

軸ごとにスクロールを制御する

overflow-xoverflow-y を使えば、横方向と縦方向を個別に制御できます。

横スクロールのみ禁止する

html, body {
  overflow-x: hidden;
}

レスポンシブ対応で「縦スクロールはOKだが、横スクロールは発生させたくない」というケースで頻繁に使います。意図しない横スクロールの原因は、width: 100vw がスクロールバー幅を含むことや、要素が画面外にはみ出ていることが多いです。

縦スクロールのみ禁止する

html, body {
  overflow-y: hidden;
}

横方向のスライダーUIなど、横スクロールだけを許可したい場合に使います。

横スクロール禁止のよくある原因と対処

原因 対処法
width: 100vw がスクロールバー幅を含む width: 100% に変更する
子要素が親の幅をはみ出している はみ出している要素を特定して修正する
position: absolute で画面外に配置 親要素に overflow: hidden を追加
ネガティブマージンで画面外にはみ出し マージンの値を見直す

補足:横スクロールの原因を特定するには、DevTools のコンソールで以下を実行すると、画面幅を超えている要素を見つけられます。

document.querySelectorAll('*').forEach(el => {
  if (el.scrollWidth > document.documentElement.clientWidth) {
    console.log(el);
  }
});

特定の要素だけスクロールを禁止する

ページ全体ではなく、特定の要素のスクロールを禁止することもできます。

.no-scroll {
  overflow: hidden;
  /* 高さを固定してスクロール領域を作らない */
  max-height: 300px;
}

逆に、ページ全体のスクロールを禁止しつつ特定の要素だけスクロール可能にしたい場合は、以下のように組み合わせます。

/* ページ全体を固定 */
html, body {
  overflow: hidden;
  height: 100%;
}

/* この要素だけスクロール可能 */
.scrollable {
  overflow-y: auto;
  max-height: 400px;
}

overscroll-behavior でスクロール連鎖を防止する

overscroll-behavior は、スクロール可能な要素の端に到達したときに親要素へスクロールが伝播する(スクロール連鎖)のを制御するプロパティです。

たとえば、モーダル内のコンテンツを最後までスクロールすると、その後に背面のページがスクロールし始める現象がスクロール連鎖です。

.modal-content {
  overflow-y: auto;
  overscroll-behavior: contain;
}

overscroll-behavior の値

動作 用途
auto デフォルト動作(親へスクロール伝播する) 通常のスクロール
contain スクロール連鎖を防止する モーダル、サイドバー、ドロップダウン
none 連鎖防止 + バウンス効果も無効化 完全にスクロールを閉じ込めたいとき

overscroll-behavior-x / overscroll-behavior-y で軸ごとに指定することも可能です。

overflow: hidden との違い

プロパティ 役割 スクロール自体
overflow: hidden はみ出たコンテンツを非表示にする スクロールそのものを禁止する
overscroll-behavior: contain スクロールの親要素への伝播を止める その要素内のスクロールはそのまま動作する

モーダル表示時に背景スクロールを禁止する

モーダルウィンドウを表示するとき、背景のページが一緒にスクロールしてしまう問題は実務で頻繁に発生します。

方法1:CSS :has() + dialog 要素(モダンな方法)

CSS の :has() セレクタを使えば、<dialog> が開いているかどうかで html のスタイルを切り替えられます。

html:has(dialog[open]) {
  overflow: hidden;
}
<dialog id="modal">
  <p>モーダルの内容</p>
  <button onclick="this.closest('dialog').close()">閉じる</button>
</dialog>
<button onclick="document.getElementById('modal').showModal()">開く</button>

JavaScript でクラスの付け外しをする必要がなく、CSSだけで完結します。

方法2:overscroll-behavior + dialog(最新の方法)

Chrome 144 以降では、overscroll-behavior が非スクロールコンテナでも動作するようになりました。::backdrop に指定することで、ダイアログ表示中の背景スクロールを防止できます。

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

この方法は overflow: hidden によるスクロールバーのガタつきが発生しません。ただし、Chrome 144+ 限定のため、現時点ではブラウザ対応状況の確認が必要です。

方法3:JavaScript でクラスを切り替える(汎用的な方法)

.scroll-lock {
  overflow: hidden;
}
// モーダルを開くとき
function openModal() {
  document.documentElement.classList.add('scroll-lock');
  document.getElementById('modal').style.display = 'block';
}

// モーダルを閉じるとき
function closeModal() {
  document.documentElement.classList.remove('scroll-lock');
  document.getElementById('modal').style.display = 'none';
}

:has() に対応していない古いブラウザでも動作する方法です。

各方法の比較

方法 CSSのみ iOS Safari スクロールバーのガタつき
:has(dialog[open]) △(不安定な場合あり) あり
overscroll-behavior ×(Chrome 144+のみ) なし
JavaScript クラス切替 × △(不安定な場合あり) あり

iOS Safari でスクロール禁止が効かない問題

iOS Safari では overflow: hiddenbody に指定しても、タッチ操作で背景がスクロールできてしまう場合があります。

これは iOS Safari がタッチスクロールを独自に処理しているためです。確実にスクロールを禁止するには position: fixed を使います。

.scroll-lock {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  /* スクロール位置をCSSで保持 */
}
// モーダルを開くとき
function openModal() {
  const scrollY = window.scrollY;
  document.body.style.position = 'fixed';
  document.body.style.top = '-' + scrollY + 'px';
  document.body.style.width = '100%';
}

// モーダルを閉じるとき
function closeModal() {
  const scrollY = document.body.style.top;
  document.body.style.position = '';
  document.body.style.top = '';
  document.body.style.width = '';
  window.scrollTo(0, parseInt(scrollY || '0') * -1);
}

ポイント

  • position: fixed にするとスクロール位置がリセットされるため、top にマイナス値で現在位置を保持する
  • モーダルを閉じたときに window.scrollTo で元の位置に戻す
  • この方法なら iOS Safari でも確実にスクロールが止まる

スクロールバー消失によるガタつきを防ぐ

overflow: hidden を適用すると、スクロールバーが消えてページ全体の幅が変わり、コンテンツが右にズレる(ガタつく)ことがあります。

対処法1:scrollbar-gutter

html {
  scrollbar-gutter: stable;
}

scrollbar-gutter: stable を指定すると、スクロールバーの有無にかかわらず常にスクロールバー分のスペースが確保されるため、ガタつきが発生しません。

対処法2:スクロールバー幅を計算してパディングを追加

function getScrollbarWidth() {
  return window.innerWidth - document.documentElement.clientWidth;
}

function openModal() {
  const scrollbarWidth = getScrollbarWidth();
  document.body.style.overflow = 'hidden';
  document.body.style.paddingRight = scrollbarWidth + 'px';
}

function closeModal() {
  document.body.style.overflow = '';
  document.body.style.paddingRight = '';
}

対処法の比較

方法 実装 ブラウザ対応
scrollbar-gutter: stable CSS 1行のみ 主要ブラウザ対応(Safari 17.4+)
パディング追加 JavaScript が必要 全ブラウザ対応

touch-action でタッチスクロールを制御する

touch-action プロパティを使うと、タッチデバイスでのスクロール操作を直接制御できます。

.no-touch-scroll {
  touch-action: none;
}
動作
auto すべてのタッチ操作を許可(デフォルト)
none すべてのタッチ操作を禁止(スクロール・ピンチ・ズーム)
pan-x 横方向のスクロールのみ許可
pan-y 縦方向のスクロールのみ許可
manipulation スクロールとピンチズームのみ許可(ダブルタップズーム無効)

touch-action: none はスクロールだけでなくピンチズームも無効にするため、ユーザビリティに影響します。地図やキャンバスなど、独自のタッチ操作を実装する場合に使用してください。

まとめ:用途別の使い分け

やりたいこと 推奨する方法 CSSのみ
ページ全体のスクロール禁止 overflow: hidden
横スクロールだけ禁止 overflow-x: hidden
モーダル内のスクロール連鎖を防止 overscroll-behavior: contain
dialog 表示時に背景を固定 html:has(dialog[open]) { overflow: hidden }
iOS Safari でも確実に背景固定 position: fixed + JavaScript ×
スクロールバーのガタつき防止 scrollbar-gutter: stable
タッチ操作を制御 touch-action: none / pan-x / pan-y

あわせて読みたい