CSSでページのスクロールを禁止する方法を解説します。
単純な overflow: hidden だけでなく、軸ごとの制御、overscroll-behavior によるスクロール連鎖の防止、モーダル表示時の背景スクロール禁止、iOS Safari での注意点まで、実務で必要になるパターンを網羅します。
overflow: hidden でスクロールを禁止する
もっとも基本的な方法は、html と body に overflow: hidden を指定することです。
html, body {
overflow: hidden;
}
これだけでページ全体のスクロールが禁止されます。
overflow プロパティの値は以下の4種類です。
| 値 | 動作 | スクロールバー |
|---|---|---|
visible |
はみ出た部分をそのまま表示(デフォルト) | なし |
hidden |
はみ出た部分を非表示にする | なし |
scroll |
はみ出た部分をスクロール可能にする | 常に表示 |
auto |
はみ出た場合のみスクロール可能にする | 必要時のみ表示 |
hidden を指定すると、コンテンツがはみ出てもスクロールバーが表示されず、はみ出た部分は見えなくなります。
軸ごとにスクロールを制御する
overflow-x と overflow-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: hidden を body に指定しても、タッチ操作で背景がスクロールできてしまう場合があります。
これは 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 |
○ |

