スマホで横スクロールが出る原因の特定手順と直し方|overflow-x:hiddenで隠す前にやること

スマホで横スクロールが出る原因の特定手順と直し方|overflow-x:hiddenで隠す前にやること

結論:犯人の要素を特定してから直す。hiddenで隠すのは最後

  • 原因の9割は「特定の1要素が画面幅を超えている」だけ=全体のレイアウト崩壊ではない
  • まずJSスニペットで幅を超えている要素を一覧に出す(目視より速い)
  • よくある犯人は 100vw・長いURLや英数字・はみ出したflex子要素・負のmargin・画面外のアニメーション用要素
  • html,bodyへの overflow-x:hidden は position:sticky を巻き添えで殺すので安易に使わない
  • どうしても隠すなら hidden ではなく overflow-x:clip を使う

「PCでは問題ないのに、スマホだと画面が少し横に動く」「右にスワイプすると白い余白が出る」——レスポンシブ実装で最後まで残りがちな不具合です。厄介なのは、見た目にはどこがはみ出しているのか分からない点で、犯人探しに時間を溶かしがちです。結論から言うと、先に「はみ出している要素」を機械的に特定してから、その要素だけを直すのが最短です。この記事では、実案件でそのまま使っている特定手順と、原因別の直し方、そして安易な overflow-x:hidden がなぜ危険なのかを整理します。

まず「はみ出している要素」を1発で特定する

横スクロールは、ページ全体が広がっているのではなく、たいてい1つか2つの要素だけが画面幅を超えています。DevToolsで要素を上から順に開いていく方法もありますが、入れ子が深いサイトでは時間がかかります。ブラウザのコンソールに次のスニペットを貼って、超えている要素だけを列挙するのが確実です。

// 画面幅を超えている要素だけをコンソールに出す
(function () {
  var limit = document.documentElement.clientWidth;
  var hits = [];
  document.querySelectorAll('*').forEach(function (el) {
    var r = el.getBoundingClientRect();
    if (r.right > limit + 1 || r.left < -1) {
      hits.push({ el: el, left: Math.round(r.left), right: Math.round(r.right) });
    }
  });
  console.table(hits);
  hits.forEach(function (h) { h.el.style.outline = '2px solid red'; });
  console.log('over:', hits.length, '/ viewport:', limit);
})();

実行すると、超過している要素がテーブルで並び、同時に赤い枠が付くので画面上でも位置が分かります。ポイントは親要素も一緒に引っかかるので、リストの一番下=もっとも深い子要素が本当の犯人であることが多いことです。上から順に見るとレイアウト全体が悪いように錯覚しますが、実際は末端の1要素だけを直せば解決します。

よくある誤解として * { outline: 1px solid red; } を全要素に当てる方法がありますが、要素が多いページでは枠だらけになって逆に分かりません。実務では上のスニペットのほうが圧倒的に速いです。

スマホ実機でしか出ない場合の確認方法

PCの検証モードでは再現せず実機だけで出る場合は、PCとスマホでスクロールバーの扱いが違うことが原因のことがあります。実機を繋いでリモートデバッグするのが正攻法ですが、手元にケーブルが無いときは、はみ出し幅をページ内に表示させてしまうのが早いです。

// 実機の画面にそのまま数値を出す簡易デバッグ
var d = document.createElement('div');
d.style.cssText = 'position:fixed;left:0;bottom:0;z-index:99999;background:#000;color:#fff;font:12px/1.4 monospace;padding:4px 6px;';
d.textContent = 'scrollW=' + document.documentElement.scrollWidth
  + ' clientW=' + document.documentElement.clientWidth;
document.body.appendChild(d);

scrollWidth が clientWidth より大きければ横スクロールが発生しています。差分がそのまま「何pxはみ出しているか」なので、この数値が原因の見当をつける材料になります。実測では、後述する 100vw 起因なら差分がスクロールバー幅とほぼ一致し、画像や表が原因なら数十〜数百pxとまとまった値になります。

原因1:100vw がスクロールバー幅の分だけはみ出す

「フルワイドの背景を作りたい」場面で width: 100vw を使うのは定番ですが、vw の 100 はスクロールバーを含んだウィンドウ幅です。縦スクロールバーが常時表示される環境(Windowsのデスクトップブラウザが典型)では、その幅ぶんだけ確実にはみ出します。手元のWindows版Chromeで実測すると差分は15px前後で、これが「PCだけ横に動く」ときの典型パターンです。

逆にスマホはスクロールバーがオーバーレイ表示のため差分が出ません。PCで出てスマホで出ない、という切り分けができたらまず 100vw を疑うのが早道です。

/* NG: スクロールバー幅ぶんはみ出す */
.section-full { width: 100vw; }

/* OK-1: 親が画面幅なら 100% で足りる */
.section-full { width: 100%; }

/* OK-2: 中央からフルワイドに広げたい場合 */
.section-full {
  width: 100%;
  margin-inline: calc(50% - 50vw);
  max-width: 100vw;
}

なお OK-2 の書き方も 50vw を使っているため厳密にはスクロールバー幅の影響を受けます。気になる場合は親側に overflow-x: clip を付けて、はみ出しをその親だけで止めるのが安全です(後述)。

原因2:flexやgridの子要素が min-width:auto で縮まない

実案件でいちばん多い犯人がこれです。flexアイテムには初期値として min-width: auto が効いており、中身より小さくなれないという制約があります。中に長いURL、英数字の型番、折り返しのないテーブルなどが入っていると、親の幅を無視して押し広げます。

/* flex の子が縮まない場合 */
.card {
  min-width: 0;   /* これで中身より小さくなれる */
}

/* grid の場合はトラック側で指定する */
.grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* 長いURLを折り返す */
.card p {
  overflow-wrap: anywhere;
}

ここで注意したいのは、min-width: 0 と overflow-wrap は役割が違うことです。前者は「箱が縮めるようにする」設定、後者は「中身が折り返せるようにする」設定で、長いURLが原因のときは両方必要になります。片方だけ入れて直らず悩むケースをよく見ます。

原因3:画面外に置いた要素が生きている

スライドインするメニュー、スクロール連動で外から入ってくる装飾、閉じている状態のドロワー——これらを transform: translateX(100%) や left: 100% で画面外に逃がしている場合、要素そのものはページ幅に数えられます。閉じているのに横スクロールが出る、という現象はほぼこれです。

対処は、閉じている状態では領域から外すことです。

.drawer {
  transform: translateX(100%);
  visibility: hidden;      /* 領域は残るがフォーカスは奪わない */
}
/* 親側で止める */
.drawer-wrap { overflow-x: clip; }

アニメーションのために display:none にできないケースが多いので、「はみ出しを親でクリップする」+「閉じている間は操作対象から外す」の2点セットで扱うのが実務的です。visibility: hidden にしておくと、閉じたドロワー内のリンクにタブキーでフォーカスが入ってしまう事故も同時に防げます。

原因4:負のmargin・padding込みの幅指定・はみ出す画像や表

残りは次のあたりを順に確認すれば、たいてい当たります。

  • 負のmargin:グリッドの隙間調整で margin-left: -15px などを付けた要素が、親の左端を越えている
  • padding込みの幅:width: 100% の要素に左右paddingが乗っている(box-sizing: border-box が効いていない領域が残っている)
  • 画像:img { max-width: 100%; height: auto; } が当たっていない箇所(記事本文の外や、後から追加したコンポーネント)
  • 表:列数の多いテーブル。これは縮めるのではなく overflow-x: auto の親で包んで、表だけ横スクロールさせるのが正解
  • 埋め込み:YouTubeやGoogleマップのiframe。width 属性の固定値が残っていることがある

表と埋め込みだけは「はみ出しを消す」のではなく「その要素の中だけスクロールさせる」のが正しい対処です。ページ全体を横に動かさずに済み、内容も欠けません。

.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

overflow-x:hidden で隠してはいけない理由

検索すると「html, body { overflow-x: hidden; } で解決」という情報が大量に出てきます。確かに横スクロールは止まりますが、これは原因を消しているのではなく、はみ出したまま見えなくしているだけです。実務では次の副作用を引き当てます。

  • position: sticky が効かなくなる:祖先に overflow の hidden/auto/scroll があると、stickyはその祖先内でしか固定されず、実質動かなくなります。追従ヘッダーや目次が突然効かなくなる原因のトップです
  • ページ内アンカーのスクロール位置がずれる:スクロールの基準となる要素が変わるため、scroll-behavior や scroll-margin-top の効きが変わります
  • 原因が温存される:はみ出した要素は消えていないので、後から別のブレークポイントやコンテンツ量で再発します

どうしても包括的に止めたい場合は、hidden ではなく clip を使います。overflow-x: clip は「はみ出しを切り取るだけでスクロールコンテナを作らない」ため、sticky を巻き添えにしません。

/* 副作用の少ない止め方 */
body {
  overflow-x: clip;
}

/* さらに範囲を絞るなら、はみ出す親だけに当てる */
.hero-wrap { overflow-x: clip; }

ただし clip は縦方向を visible のまま残せる代わりに、その要素より内側では position:fixed の基準が変わる場合がある点に注意してください。既存サイトへ後から入れるときは、追従ヘッダーとモーダルの動作を必ず確認します。順序としては「①犯人を特定して直す → ②どうしても残るなら該当する親だけ clip → ③body全体の clip は最後の手段」です。hidden は選択肢から外して構いません。

再発させないためのチェックリスト

公開前とコンポーネント追加時に、この5項目だけ確認しておくと横スクロールはほぼ出なくなります。

  • コンソールのスニペットで超過要素が0件であることを、375px幅と横向き(812px前後)の両方で確認する
  • 新規に 100vw を書いていないか(レビュー時にgrepする)
  • flex・gridの子に長い文字列が入る可能性がある箇所に min-width: 0 と overflow-wrap があるか
  • テーブルと埋め込みは、スクロール用のラッパで包まれているか
  • 閉じているドロワー・スライドイン要素が、親でクリップされているか

横スクロールは「見た目の小さな不具合」に見えますが、スマホで指が横に滑るとレイアウトが破綻して見え、そのまま離脱につながります。特にお問い合わせ導線の手前で起きると機会損失に直結するため、公開前チェックに入れておく価値のある項目です。

関連記事

多言語・ベトナム語サイトの制作でお困りではありませんか?

フォント・文字化け・WordPress多言語化・サーバー選定まで、多言語サイト制作の実績を持つ制作会社が一括対応します。お見積り・ご相談は無料です。

多言語サイト制作の詳細を見る →

すぐに相談したい方は お問い合わせフォームへ

関連記事(コーディング実務Tips)

Web制作Tips(コーディング実務)カテゴリの最新記事