overflow: hidden と overflow: clip、どう使い分ける?
CSSを書いていると、何かとはみ出します。
画像だったり、装飾だったり、画面の外まで伸ばした要素だったり。
そんなとき、以前の私は割と気軽にこれを書いていました。
overflow: hidden;
「はみ出したものを隠したいから hidden」
特に深く考えることもなく、よく使っていた指定です。
ところがある日、position: sticky を使った実装で少し困ることがありました。
ちゃんと、
position: sticky;
top: 0;
と指定しているのに、思ったようにstickyしてくれない。
原因を調べていくと、祖先要素に「はみ出させた要素による横スクロール対策」として入れていた
overflow-x: hidden;
が影響していました。
そこでたどり着いたのが、
overflow-x: clip;
です。
hidden を clip に変えると、はみ出した部分はそのまま隠しつつ、stickyも無事に動きました。
「それなら、単純にはみ出しを隠しているところは clip でいいのでは?」
そう思って、見切れさせるためだけに使っていた hidden をいくつか clip に変更しました。
このときは、かなり clip 派になりかけました。
でも改めて調べてみると、そう単純でもありません。
clip は hidden の新しい書き方でも、上位互換でもない。
見た目はよく似ていますが、それぞれが持っている性質にはちゃんと違いがあります。
せっかくなので、改めて整理してみることにしました。
見た目はよく似ている
まず、単純にはみ出した要素を隠すだけなら、
.box {
overflow: hidden;
}
でも、
.box {
overflow: clip;
}
でも、見た目はほとんど同じです。
どちらも、要素の外にはみ出した部分を表示しません。
では何が違うのか。
大きな違いのひとつが、その要素をスクロールできる領域として扱うかどうかです。
hiddenは「隠れているけど、スクロールできる」
overflow: hidden は、名前だけを見ると「はみ出した部分を非表示にする」指定に見えます。
もちろんそれも正しいのですが、実際にはもう少し仕事をしています。
overflow: hidden を指定した要素は、スクロールコンテナになります。
スクロールバーは表示されず、ユーザーが普通に操作してスクロールすることはできません。
ただし、JavaScriptから、
element.scrollTo({
left: 100,
});
のようにスクロール位置を変更することはできます。
フォーカスされた要素を表示するために、ブラウザがスクロール位置を動かすこともあります。
つまり hidden は、
はみ出した部分を見えなくしているけれど、スクロールできる領域として残している
という状態です。
clipは「ここから先はカット」
一方、overflow: clip はもっとシンプルです。
.box {
overflow: clip;
}
要素からはみ出した部分を、その位置でクリップします。
hidden のようにスクロールコンテナは作りません。
JavaScriptからスクロール位置を変更することもできません。
なので、
- 画像を枠の中でトリミングしたい
- アニメーション中にはみ出した部分を見せたくない
- 装飾が画面外にはみ出すのを切りたい
- 見切れさせること自体が目的
といった、
「ここから外は表示されなくていい」
という場面では、clip の方が目的に近い指定です。
そして、この違いが今回引っかかった position: sticky にも関係していました。
横スクロールを消しただけなのに、stickyが動かない
今回のきっかけになったのは、横にはみ出すデザインでした。
不要な横スクロールを防ぐために、
.sticky-parent {
overflow-x: hidden;
}
を指定していました。
その中に、
.sticky-item {
position: sticky;
top: 0;
}
という要素がありました。
ところが、これが思ったようにstickyしてくれません。
position: sticky は、スクロールに関係する祖先要素の影響を受けます。
そして overflow: hidden は、スクロールコンテナを作ります。
ここでもうひとつ少しややこしいのが、overflow-x と overflow-y の関係です。
何も指定していない overflow-y の初期値は visible ですが、
overflow-x: hidden;
のように片方へ hidden を指定すると、もう片方の visible は算出値として auto になります。
つまり、指定としては overflow-x: hidden の一行でも、算出値は
overflow-x: hidden;
overflow-y: auto;
となります。
つまり、
「横にはみ出す部分だけ見えなくしたい」
というつもりで書いた一行によって、スクロールコンテナが作られ、縦方向のstickyにも影響していました。
そこで、
.sticky-parent {
overflow-x: clip;
}
に変更。
clip はスクロールコンテナを作らないので、横にはみ出した部分をカットしつつ、stickyへの影響も避けられました。
これが、私が clip を積極的に使うようになったきっかけです。
じゃあ、全部clipでいい?
ここまで見ると、
「スクロールさせないなら、全部 clip でよさそう」
という気もします。
私も一度そう思いました。
でも、hidden と clip の違いはスクロールだけではありません。
もうひとつ大きいのが、BFC(Block Formatting Context)です。
hiddenはBFCを作る。clipは作らない
overflow: hidden を指定すると、その要素には新しいBFCが作られます。
BFCについて詳しく説明し始めると別の記事になりそうなのでざっくり言うと、要素の内側と外側のレイアウトをある程度独立させる仕組みで、marginの相殺など、レイアウトの挙動に影響します。
そのため、
.box {
overflow: hidden;
}
を単純に、
.box {
overflow: clip;
}
へ置き換えると、
「はみ出した部分の見た目は同じなのに、なぜかレイアウトが変わった」
ということもあり得ます。
hidden が、はみ出しを隠す以外の仕事もしていたからです。
一方、overflow: clip 単体ではBFCを作りません。
もし、
「はみ出しはclipしたい。でもBFCは必要」
という場合には、
.box {
overflow: clip;
display: flow-root;
}
のように、役割を分けて指定することもできます。
役割を分けて書いた方が「何のための指定なのか」が分かりやすい場面もあります。
見た目が同じでも、単純置換はしない
こうして整理すると、hidden と clip はかなり似て見えるのに、内部では違うことをしています。
overflow: hidden
- はみ出した部分を表示しない
- スクロールコンテナを作る
- JavaScriptなどからスクロールできる
- BFCを作る
overflow: clip
- はみ出した部分を表示しない
- スクロールコンテナを作らない
- JavaScriptなどからスクロールできない
- BFCを(単体では)作らない
このうち、スクロールコンテナを作るかどうかの違いが、今回の position: sticky の挙動にも影響していました。
見た目だけなら、
overflow: hidden;
を、
overflow: clip;
に変えても何も起きないことは多いです。
でも、hidden が持っていたスクロールやレイアウト上の性質まで必要だった場合には、同じ結果にはなりません。
なので、単純に一括置換するものではなさそうです。
結局、どう使い分ける?
いろいろ調べましたが、今のところ私はかなりシンプルに考えています。
見切れさせたいだけなら clip
画像や装飾、アニメーションなど、
「ここから外は見えなくていい」
というだけなら、まず clip を考えます。
.image {
overflow: clip;
}
余計なスクロールコンテナを作らないので、指定している目的も分かりやすいです。
今回のように、position: sticky が関係する場所でもこちらが扱いやすいことがあります。
スクロール可能な領域として残したいなら hidden
一方で、
- JavaScriptなどから中身をスクロールさせたい
- スクロールコンテナとして扱いたい
hiddenが作るBFCも含めて利用したい
といった場合には、
overflow: hidden;
が合っています。
また、BFCだけが必要なのであれば display: flow-root など、別の方法でその役割を明示する選択肢もあります。
「隠す」のか、「切る」のか
overflow: hidden と overflow: clip。
どちらも、結果だけを見るとはみ出した部分が見えなくなります。
以前は私も、
「はみ出したからhidden」
くらいの感覚で使っていました。
でも今回改めて調べてみると、
hidden は、見えなくした上でスクロールできる領域として残す。
clip は、その場所でカットする。
似ているようで、役割はちゃんと違います。
どちらが新しいとか、どちらが優れているとかではなく、
「ここでは何をしたいんだっけ?」
から考えるのが、一番分かりやすそうです。
ただ見切れさせたいのか。
スクロールできる領域として残したいのか。
BFCのような別の役割まで overflow に任せていないか。
たった一行のCSSですが、見た目以外にも意外といろいろなことをしています。
私自身、これから overflow: hidden を書くときは、
「ここ、本当にhidden?」
と一度考えることになりそうです。