- hidden は、スクロールできる箱である 見えないだけで、スクロール文脈は生まれている
- clip との違いは、そこだけ 7項目の対比表
- sticky が効かなくなる理由 基準になる祖先が入れ替わる
- overscroll-behavior でラバーバンドを止める -x に限る理由
- border-radius と、消える装飾 クリップの基準はパディングボックス
- body の overflow は、ビューポートへ伝播する html に書くべき場面
- まとめの早見表 どちらを選ぶか
hidden は、スクロールできる箱である
overflow:hidden は「はみ出しを隠す」指定として使われます。
間違いではありませんが、正確にはこうです。
overflow:hidden はスクロール可能な箱を作り、
そのスクロールバーを表示しない——それだけです。
スクロール文脈(scroll container)は生まれています。
証拠は簡単に確かめられます。
// overflow:hidden の要素でも、JSからは動かせる box.scrollLeft = 200; // 動く box.scrollTop = 100; // 動く // キーボード操作やアンカー移動でも、勝手にずれることがある
つまり hidden は「隠す」というより「スクロールできるが、操作手段を見せない」状態です。
この性質が、いくつかの副作用の源になります。
clip との違いは、そこだけ
overflow:clip は、スクロール文脈を作らずにクリップだけする指定です。
比べると、違いは1点に集約されます。
| 項目 | hidden | clip |
|---|---|---|
| はみ出しをクリップする | する | する |
| スクロールバーを出す | 出さない | 出さない |
| スクロール文脈を作る | 作る | 作らない |
JS で scrollLeft が動く | 動く | 動かない |
子孫の position:sticky | 効かなくなる場合 | 影響しない |
| スクロール位置の復元対象 | なる | ならない |
clip。
「はみ出しを切りたいだけ」なら、余計な文脈を作らないほうが安全です。
逆に JS でスクロール位置を制御したいなら hidden が要ります。
なお clip は比較的新しい値です。対応していない環境では
宣言そのものが無視され、visible のままになります。
クリップされないと困る場面では、フォールバックを考えてください。
sticky が効かなくなる理由
position:sticky の要素は、最も近いスクロール文脈を基準に張り付きます。
通常はビューポートです。
.outer { overflow: hidden; } ← スクロール文脈になる
.outer .bar {
position: sticky;
top: 0; ← 基準が .outer に変わる
}
基準が .outer に移ると、.outer の中でのスクロール量を見ることになります。
ところが .outer 自身はスクロールしません(中身が収まっている、あるいは外側がスクロールしている)。
動く余地がゼロなので、張り付く動作も起きません。
要素は消えず、エラーも出ません。ただスクロールに追従しなくなるだけです。 原因が sticky 自身ではなく祖先にあるため、見つけにくい部類の不具合です。
.outer { overflow: clip; } ← 文脈を作らない
.outer .bar {
position: sticky;
top: 0; ← 基準はビューポートのまま
}
body に横方向のクリップを入れる必要がありました。
ターミナル風のバーを position:sticky にしている記事があったため、
hidden ではなく clip を選びました。
実機で確認して、sticky が保たれることも確かめています。
overscroll-behavior でラバーバンドを止める
iOS Safari では、縦にスクロールしながら指を左右に振るとページ全体がずれ、離すと戻ります。 いわゆるラバーバンド(オーバースクロール)です。
overflow-x:hidden を入れてもこれは残ります。
スクロールを止めることと、端での弾性を止めることは別だからです。
| 値 | スクロールの連鎖 | 端での弾性 |
|---|---|---|
auto(既定) | 親へ連鎖する | 起きる |
contain | 止める | 起きる |
none | 止める | 止める |
html {
overscroll-behavior-x: none;
}
-x に限る理由。
overscroll-behavior: none と全方向に指定すると、
縦の「引っ張って更新」も止まります。
モバイルの標準操作を奪うことになるので、
止めたい方向だけを指定するのが安全です。
overflow-x:hidden では止まらず、
overscroll-behavior-x:none を入れて解消しています。
これが効いたということは、はみ出しは無く純粋な弾性だったという裏付けにもなりました。
border-radius と、消える装飾
角丸のカードに、枠の四隅へ装飾を置こうとしたときの話です。
.card {
position: relative;
border: 1px solid #333;
border-radius: 10px;
overflow: hidden;
}
.card::before {
content: "";
position: absolute;
top: -1px; left: -1px; ← 枠に重ねたい
width: 13px; height: 13px;
border-width: 1px 0 0 1px; ← L字のかぎ括弧
}
overflow によるクリップはボーダーの内側で行われます。
-1px に置いた1pxの線は、まるごと範囲外。
さらに border-radius の丸みに沿って角が削られるため、
この装飾は一度も表示されません。
回避策は3つあります。
| 方法 | 代償 |
|---|---|
装飾を内側(top:3px など)に置く | 枠に重ねる意匠は諦める |
overflow を外す | 中身のはみ出しが切れなくなる |
| 装飾を親要素側へ移す | 構造が一段増える |
-1px によるものかは、切り分けていません。
body の overflow は、ビューポートへ伝播する
ページ全体の横スクロールを止めたいとき、body に書くか html に書くかで迷います。
仕様上の扱いはこうです。
overflow は、まず html から取られます。
html が visible のときに限り、body の値が代わりに使われます。
伝播した側の要素は、自身の使用値が visible になります。
つまり body{overflow-x:hidden} だけでも、
html に何も指定が無ければビューポートに効きます。
逆に html 側に何か指定があると、body の値は伝播せず
body という箱の中の話になります。
/* ビューポートを止めたいなら html に書く */ html { overflow-x: hidden; } /* body 自身の箱をクリップしたいなら body に書く */ body { overflow-x: clip; }
html{scroll-behavior:smooth} を先に書いていたため、
body だけでは足りず html にも書く必要がありましたが、
それが伝播の停止によるものか別の要因かは切り分けていません。
迷う場面では、意図する側に明示的に書くのが確実です。
hidden と clip、どちらを選ぶか
場面を選ぶと、両者の結果を並べて表示します。 同じになる場面と、決定的に違う場面があります。
差が出るのはスクロール文脈が関わる場面だけです。
単にはみ出しを切りたいなら、どちらでも結果は同じ。
だからこそ、余計な文脈を作らない clip を既定にしておくのが素直だと思います。
まとめの早見表
| やりたいこと | 書くもの |
|---|---|
| はみ出しを切るだけ | overflow: clip |
| JS でスクロール位置を制御したい | overflow: hidden |
| 子孫に sticky がある | clip(hidden は避ける) |
| ページの横スクロールを止める | html { overflow-x: hidden } |
| iOS の左右の揺れを止める | overscroll-behavior-x: none |
| 引っ張って更新は残したい | none を全方向に書かない |
| 角丸の枠に装飾を重ねたい | overflow を外すか、内側に置く |
この記事のまとめ
hiddenはスクロール可能な箱を作り、バーを見せないだけ。文脈は生まれている。clipは文脈を作らずクリップする。違いはそこだけ。- 祖先の
hiddenは子孫の sticky の基準を奪う。動く余地が無く、張り付かなくなる。 - ラバーバンドは
overflowでは止まらない。overscroll-behavior-x: none。 - 全方向に
noneを書くと引っ張って更新も止まる。方向を限る。 - クリップの基準はパディングボックス。枠外に置いた装飾は消える。
- ビューポートの
overflowはhtmlから取られ、htmlがvisibleのときだけbodyが使われる。
- 縮まないと、あふれる
min-width:0 と overflow の関係 - クリップとスクロールの境界(この記事)
hidden / clip / overscroll-behavior - 背景と合成レイヤー
::before / fixed / will-change