「自分の環境では出ないのに、クライアントの画面だけ横スクロールが出る」
CSS で vw を使ったレイアウトを組んだあと、こういう報告を受けたことはないでしょうか。しかもヘッドレスブラウザで自動チェックしても0件。再現しないバグほど厄介なものはありません。
答えは単純で、100vw / 50vw は縦スクロールバーの幅を含むからです。そしてヘッドレスブラウザには、そのスクロールバーが存在しません。
この記事で分かること
vwとパーセント(%)で「画面幅」の定義がズレる理由- ヘッドレスの自動テストで横スクロールを検出できないケースがあること
- スクロールバー幅に依存しない書き方と、実務的な落としどころ
背景:デザインどおりに「はみ出させる」実装
コーポレートサイトのトップページで、こういうデザインがありました。
セクションの中身は中央寄せのコンテナ(最大幅 1100px)に入っているのに、マスコットのイラストだけコンテナの外側にはみ出して配置されている。よくある装飾です。
素直に書くとこうなります。
.section__inner {
max-width: 1100px;
margin-inline: auto;
}
.section__deco {
position: absolute;
left: -72px; // インナーの外側へ
}
これはウィンドウが十分に広いときは正しく動きます。ところがウィンドウを狭めると、インナーが画面幅いっぱいに広がるため、-72px が画面の外に出てしまう。イラストが左半分だけ見切れる状態になりました。
結論:下限を 50vw - 50% で切ったら、別のバグを生んだ
「画面の外に出ないように下限を設ける」— 発想は正しいです。書いたのがこれ。
.section__deco {
// インナー左端から画面左端までの距離 = 50vw - 50%
left: max(-72px, calc(-1 * (50vw - 50%)));
}
50vw は画面幅の半分、50% は親(インナー)の幅の半分。差を取れば「インナーの外側に残っている余白」が求まります。これを下限にすれば画面外に出ない、という理屈です。
見切れは直りました。そして今度は、1150px 前後で横スクロールバーが出るようになりました。
しくみ:vw と % は「画面幅」の定義が違う
ここが本題です。2つの単位は、似ているようで基準が違います。
| 単位 | 基準 | スクロールバーを |
|---|---|---|
100vw |
ビューポート幅 | 含む |
width: 100% |
親要素のコンテンツ幅 | 含まない |
縦スクロールバーが表示されている環境では、この2つがスクロールバーの幅(デスクトップのブラウザで概ね15px前後)だけズレます。
先ほどの式に当てはめると、こうなります。
ウィンドウ幅(スクロールバー込み)= 1150px
実際に表示できる幅(clientWidth) = 1135px ← 15px 少ない
50vw = 575px ← 1150 が基準
インナーの 50% = 550px ← 1100 が基準
50vw - 50% = 25px
max(-72px, -25px) は -25px を選びます。つまり装飾はインナー左端から25px外側に置かれる。ところが実際に使える余白は 17.5px しかない(1135 と 1100 の差の半分)。差の 7.5px が画面をはみ出し、横スクロールが発生します。
ハマりどころ:ヘッドレスブラウザでは再現しない
いちばん時間を溶かしたのはここです。
Playwright のヘッドレスモードでウィンドウ幅を変えながら検証しました。結果は全部OK。
W=1250: scrollWidth=1250 clientWidth=1250 → 横スクロールなし
W=1200: scrollWidth=1200 clientWidth=1200 → 横スクロールなし
W=1150: scrollWidth=1150 clientWidth=1150 → 横スクロールなし
scrollWidth と clientWidth が一致している=はみ出しゼロ。数値上は完璧です。
ヘッドレスブラウザにはスクロールバーが無いからです。 100vw と 100% が完全に一致してしまうため、この種のバグは原理的に検出できません。
「自分の測定では出ないが、相手の画面では出る」— このパターンを見たら、まずスクロールバー幅を疑うのが早いです。
対処:3つの選択肢
1. overflow-x: hidden で横方向だけ止める(採用した方法)
body {
overflow-x: hidden;
}
いちばん確実です。装飾のはみ出し自体はデザイン上の意図なので、「はみ出させる。ただし横スクロールはさせない」という宣言になります。
副作用として position: sticky が効かなくなるケースがあるので、追従ヘッダーなどを使っている場合は動作確認が要ります(position: fixed なら影響しません)。
2. スクロールバー幅を計算に入れる
JavaScript でスクロールバー幅を測ってカスタムプロパティに入れる方法もあります。
document.documentElement.style.setProperty(
'--sbw',
`${window.innerWidth - document.documentElement.clientWidth}px`
);
left: max(-72px, calc(-1 * (50vw - 50% - var(--sbw, 0px) / 2)));
正確ですが、装飾の位置決めのために JS を1本増やすのが釣り合うかは考えたいところです。
3. 100dvw などの新しい単位に逃げない
dvw / svw / lvw はビューポートの高さの変動(モバイルのURLバー)に対応する単位で、スクロールバーの問題は解決しません。ここは勘違いしやすいポイントです。
まとめ
100vwはスクロールバーを含み、width: 100%は含まない。デスクトップでは15px前後ズレる- そのズレはヘッドレスブラウザでは発生しないため、自動テストをすり抜ける
- 「自分の環境で再現しない横スクロール」はスクロールバー幅を最初に疑う
- 実務的には
overflow-x: hiddenで横方向だけ止めるのが手堅い。厳密にやるなら JS でスクロールバー幅を測る
数値が「異常なし」と言っていても、実機で見ている人の報告のほうが正しいことがあります。測定できていないだけという可能性を、いつも一つ残しておきたいところです。
制作のご相談は、お問い合わせフォームからお気軽にどうぞ。