LoginSignup
3
2

More than 1 year has passed since last update.

html, body { overflow-x: hidden; } 両方に設定するのはなぜ?

Posted at

CSSに以下の“おまじない”を書いておくと、コンテンツが画面幅をはみ出していた場合でも横スクロールが発生しないようにできますが、そもそもなぜhtmlとbodyの両方に"overflow-x: hidden"を設定する必要があるのでしょうか?

css(例1)
html, body {
  overflow-x: hidden;
}

このコードを理解するカギは、CSSのOverflow Viewport Propagationという仕組みです。詳細は後述するとして、まずはoverflowプロパティの値をvisiblehiddenに変えてhtml要素とbody要素に適用した場合に、境界をはみ出したコンテンツをブラウザーがどのように表示するかを確認してみましょう。

css(例2)
html {
  overflow: visible; /* 初期値 */
}
body {
  overflow: hidden;
}

サンプルコードはこちら:
https://codepen.io/kaz_hashimoto/pen/jOKQQQx

visiblehiddenの組み合わせ4通りについてデスクトップ版Chromeで表示すると、結果は図1に示した画面A〜Dのようになりました。

図1
fig1.png
赤い境界線のボックスは<html>要素、青い境界線は<body>要素です。ビューポートとの境界を見分けられるように、<html>と<body>の寸法はウインドウよりも小さくしてあります。

図1の結果を要約すると

画面 <html> <body> scroll はみ出し はみ出し方
A visible visible Yes Yes window < content
B visible hidden No Yes html < content ≤ window
C hidden visible No Yes html < content ≤ window
D hidden hidden No No content ≤ body

※「はみ出し方」の記号の意味は直感的ではありますが、
<:境界で content がクリップされない、≤:クリップされる。

画面B, Cではoverflow: hiddenが効いてないように見えますね。CSSに指定したはずのhiddenの値はどこに行ってしまったのでしょうか?

ここにOverflow Viewport Propagationの仕組みが働いているのです(図2)。

図2
fig2.png

Overflow Viewport Propagationとは

CSS Overflow Module Level 33.5. Overflow Viewport Propagationには次のように書かれています。わかりやすくするため、1つのパラグラフを3つの文に分けて説明します。

仕様1

UAs must apply the 'overflow-*' values set on the root element to the viewport when the root element’s 'display' value is not 'none'.

(DeepL翻訳)

UA は、ルート要素の 'display' の値が 'none' でない場合、ルート要素に設定された 'overflow-*' 値をビューポートに適用しなければならない。

たとえば画面Cの場合、<html>に設定したhidden値がビューポートに適用されます(図3a)。

図3a
fig3a.png

仕様2

However, when the root element is an [HTML] <html> element (including XML syntax for HTML) whose 'overflow' value is 'visible' (in both axes), and that element has as a child a <body> element whose 'display' value is also not 'none', user agents must instead apply the 'overflow-*' values of the first such child element to the viewport.

(DeepL翻訳)

しかし、ルート要素が[HTML]の<html>要素(HTMLのXML構文を含む)であり、その'overflow'の値が(両軸とも)'visible'で、その要素が子として<body>要素を持っている場合、ユーザーエージェントは代わりにその最初の子要素の 'overflow-*' 値をビューポートに適用しなければならない。

たとえば画面Bの場合、<html>に設定したoverflowの値がvisibleなので、<body>に設定したhidden値がビューポートに適用されます(図3bの左)。

図3b
fig3b.png

仕様3

The element from which the value is propagated must then have a used 'overflow' value of 'visible'.

(意訳)

値の伝達元の要素はその後、'overflow'の使用値が'visible'でなければならない。

たとえば画面Bの場合、<body>に設定したhidden値がビューポートに伝達されたので、伝達元の<body>側が使用値としてvisibleになります1

図3c
fig3c.png

画面B, Cでoverflow: hiddenが効いてないように見える理由もこれでわかりました。つまり、

  • <html>と<body>に設定していたoverflowの値が両方とも使用値としてvisibleに変わるため(図3c)、コンテントのテキストが境界からはみ出してしまう。
  • ビューポート側にはoverflowの値にhiddenが適用されるため、<html>の境界からもはみ出したテキストは画面の端でクリッピングされて、画面はスクロールしない。

参考)仕様の解釈はこちらの回答も参考にしました。
stackoverflow#14718319: Why does overflow-x: hidden make my absolutely positioned element become fixed?

スマホでは状況が違う

画面B, Cでスクロールしないのなら、冒頭の問いに戻って、

横スクロールを防止するために
なぜhtmlとbodyの両方に"overflow-x: hidden"を設定する必要があるのでしょうか?

そこで、画面Bと同じページをスマホで表示してみましょう。iOSとAndroidのいくつかのバージョンで試した結果は図4のとおり。画面がスクロールしてしまうものがあります。

図4
visible-hidden_1080p.png
※使用したブラウザは、Safari (iOS), Chrome (Android)です。

iOSの場合、15.2はスクロールが発生しますが、同じv.15系でも15.5はスクロールが発生しません。一方、Androidはv10, 11, 12すべてスクロールが発生しました2

これらの事実を踏まえると、デスクトップでもスマホでも確実に横スクロールを防止できるパターンは画面Dしか残されていません。すなわち、htmlとbodyの両方に"overflow-x: hidden"を設定する必要があるわけですね。やっと理解できました。

補足

ちなみに、CSS仕様書の3.5. Overflow Viewport Propagationに、スマホでの表示に関する注記があります。しかし、この注記が導入3されるきっかけとなったissue4は図4に示した現象とは異なるため、この現象との関連性はわかりませんでした。

Note: 'overflow: hidden' on the root element might not clip everything outside the Initial Containing Block if the ICB is smaller than the viewport, which can happen on mobile.

(DeepL翻訳)

注:ルート要素の 'overflow: hidden' は、初期包含ブロック(ICB)がビューポートより小さい場合5、ICBの外側のすべてをクリップしないことがあります(モバイルで発生することがあります)。

  1. overflowプロパティに関して、Window.getComputedStyle()が返す値やChrome DevToolsのComputedタブには計算値が表示されるため、これらの方法では使用値の変化を確認できません。

  2. さらにiOSの場合と異なり、ページ上部にposition: fixedで配置してある黒いボックスも一緒に横へスクロールしてしまいます。

  3. Working Draft, 2 December 2021

  4. [css-overflow] Clarify what rect clips the root element with overflow:hidden on mobile environments with dynamic toolbar #5646

  5. 定義によると、初期包含ブロックとはルート要素(html)の包含ブロックだから、CSS2.1§10.1の定義によると"it has the dimensions of the viewport"とあるので、「ICBがビューポートより小さい場合」の意味がよくわかりませんでした。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2