zudo-css-wisdom
GitHub リポジトリ

検索したい単語を入力

いつでも検索バーを開ける

Overflow の clip と hidden

軸ごとの overflow セマンティクス — 純粋なペイントクリッピングの clip とスクロールコンテナを作る hidden、強制変換ルール、Grid の描画突き抜けバグ

問題

AIエージェントは、望まないオーバーフローに対する万能の修正として overflow: hidden を使いがちです。これは正しくありません。hidden はペイントをクリップする以上のことを行い、その余分な挙動が特徴的なバグのクラスを生み出します。実際のレビューで見つかった次の2つの不具合は、どちらも overflow のセマンティクスに起因するものでした。

  • アプリシェルのグリッド(grid-template-rows: auto 1fr auto)で、ボディ行の大きすぎるコンテンツが隣のフッター行を突き抜けて描画された。min-height: 0 によってトラックのジオメトリは安定していたが、デフォルトの overflow: visible がコンテンツを次の行の上に重ねて描画させていた。

  • 素朴な修正 — ボディへの overflow: hidden — は突き抜けを止めたが、全幅の子要素のインライン軸の box-shadow をパディング端で平らに切断してしまった。

どちらの不具合も、あまり知られていない2つのルールから生じます。第一に、hidden はスクロールコンテナを作り、頼んでいないスクロールの副作用を持ち込みます。第二に、overflow の2つの軸は独立ではありません。visible は、もう一方の軸の hiddenscrollauto と共存できず、暗黙のうちに auto へ強制変換されます。このパネルの正しい修正は hidden でもラッパー要素でもなく、overflow-x: visible; overflow-y: clip でした。

解決方法

実際に必要なものに応じて overflow の値を選びます。

  • 本物のスクロール領域(ユーザーによるスクロール、scrollTo、1行省略記号のレシピ)が必要なら autoscrollhidden を使います。

  • ペイントのクリッピングだけが必要なら clip を使います。スクロールコンテナを作らず、新しいフォーマッティングコンテキストを強制せず、ブラウザはスクロール機構を完全に省略でき、overflow-clip-margin も使えます。

  • 一方の軸をクリップしつつ、もう一方の軸で装飾(シャドウやアウトライン)を生かしたいなら overflow-x: visible; overflow-y: clip(またはその転置ペア)を使います。visible + clip は、強制変換ルールがそのまま残す唯一の混合ペアです。

clip と hidden の違い

どちらの値もボックス端でペイントをクリップします。それ以外はすべて異なります。

挙動overflow: hiddenoverflow: clip
ペイントのクリップあり(パディングボックスで)あり(overflow clip edge で。overflow-clip-margin で調整可能)
スクロールコンテナの生成あり — プログラムからスクロール可能(scrollTopscrollIntoViewなし — ボックスは一切スクロールできない
フォーカス/フラグメント遷移でのスクロールあり。スクロールバーがないままコンテンツが静かにずれ、元に戻す手段がないなし
スクロールアンカリングへの参加ありなし
新しいフォーマッティングコンテキストの確立ありなし
もう一方の軸の visible との組み合わせ不可 — auto へ強制変換する
/* A scroll container you cannot see or operate */
.card {
  overflow: hidden;
}

/* Pure paint clipping */
.card {
  overflow: clip;
}

スクロールコンテナかどうかの違いは、机上の話ではありません。hidden のボックスは、ブラウザが「内部の何かを見せる必要がある」と判断するたびにスクロールします。キーボードフォーカスが画面外の子孫要素に移ったとき、location.hash による遷移、ライブラリからの scrollIntoView などです。コンテンツはずれ、戻すためのスクロールバーは存在せず、レイアウトは壊れたように見えます。clip のボックスは何をしてもスクロールできません。

overflow: hidden はスクロールコンテナである

Note

クリッピングの背後にフォーカス可能なコンテンツを隠すことは、それ自体がアクセシビリティ上の問題の兆候です。キーボードユーザーは見えない要素にフォーカスすることになります。このデモはスクロール可能性の違いを示すためだけのものであり、インタラクティブなコンテンツの本当の解決策は「クリップしないこと」です。

軸ごとの強制変換ルール

overflowoverflow-xoverflow-y のショートハンドですが、2つの軸は独立ではありません。片方の軸が hiddenscrollauto のとき、もう一方の軸の visibleauto に、cliphidden に計算されます。つまり、そっとしておきたかった軸に、暗黙のスクロールコンテナが生まれます。

宣言計算結果効果
overflow-y: hidden(x は visible のまま)overflow-x: auto; overflow-y: hiddenx 軸に暗黙のスクロールコンテナ
overflow-x: hidden(y は visible のまま)overflow-x: hidden; overflow-y: autoy 軸に暗黙のスクロールコンテナ
overflow-x: clip; overflow-y: autooverflow-x: hidden; overflow-y: autoclip がスクロールコンテナ系の値へ昇格される
overflow-x: visible; overflow-y: clip変化なし唯一の合法な混合ペア

visible + clip は、書いたとおりに生き残る唯一の組み合わせです。したがって「ブロック軸のオーバーフローはクリップし、インライン軸の装飾は生かす」と表現できる唯一の方法でもあります。

/* What you wrote */
.track {
  overflow-y: hidden;
}

/* What you got */
.track {
  overflow-x: auto; /* forced — a scroll container you never declared */
  overflow-y: hidden;
}

/* The only way to clip one axis and keep the other truly visible */
.track {
  overflow-x: visible;
  overflow-y: clip;
}
hidden の隣の visible は auto に強制変換される

Grid の描画突き抜けバグ

Grid や Flex のトラックでは、コンテンツが大きすぎるときの不具合が「ジオメトリ」と「ペイント」という2つの独立した層で起こります。min-height: 0(または min-width: 0)が修正するのはジオメトリです。トラックがコンテンツに合わせて伸びるのを止めます。しかしペイントには何もしません。デフォルトの overflow: visible のままでは、アイテムのコンテンツはトラック端を越えて描画され続け、下のフッター行に重なり、パネルの外まで逃げ出します。Grid アイテムはドキュメント順にアトミックに描画されるため、フッターに不透明な背景を付ければ自分の帯だけは覆い隠せますが、それは重なりを隠すだけです。オーバーフローはその下のパネルボーダーも越えて続きます。トラックのアイテムがクリップするまで、コンテンツを封じ込めるものは何もありません。

.app-panel {
  display: grid;
  grid-template-rows: auto 1fr auto;
  height: 100dvh;
}

.app-panel__body {
  /* Step 1: geometry — let the track shrink below its content */
  min-height: 0;
  /* Step 2: paint — stop content from overlaying the footer */
  overflow-y: clip;
}

両方のステップが必要です。min-height: 0 だけでは突き抜けが残り、overflow-y: clip だけではトラックが伸びたままです。さらに、クリップは軸別の形でなければなりません。ショートハンドの overflow: clip(や overflow: hidden)はインライン軸もクリップしてしまい、シャドウやアウトラインを切断します — 次のセクションで扱います。min-width: 0 / min-height: 0 のリセット自体は Flexboxパターン で解説しています。

Grid 行の描画突き抜けと clip による修正

シャドウの切断と軸別指定による解決

両軸のクリップは、レイアウト上のオーバーフローだけでなく装飾も切断します。overflow: hidden のコンテナ内にある全幅の子要素は、インライン軸の box-shadow をコンテナのパディング端で平らに切断されます。シャドウは子のボーダーボックスの外側に描画され、コンテナは自分の外側をすべてクリップするからです。軸別のペアはブロック軸のクリップを保ちながらインライン軸を解放するので、シャドウやアウトラインは完全な形で描画されます。シャドウの作り方自体は スムーズなシャドウトランジション で解説しています。

/* NG: shears the shadows flat at the inline edges */
.card-stack {
  overflow: hidden;
}

/* OK: clips the block axis, lets inline-axis shadows breathe */
.card-stack {
  overflow-x: visible;
  overflow-y: clip;
}
overflow: hidden はシャドウを切断し、visible + clip は保持する

どちらのスタックも3枚目のカードを下端でクリップしています — ブロック軸のクリップは同じように機能します。異なるのはインライン軸だけです。左のスタックはすべてのシャドウを破線ボーダーの位置で平らに切断し、右のスタックは背景へシャドウがにじみ出ることを許しています。

overflow-clip-margin

clip には hidden にないもう1つの能力があります。クリップ境界を外側へ押し広げられることです。overflow-clip-margin は、コンテンツがクリップされる前にパディングボックスの外側どこまで描画してよいかを設定します。フォーカスリング、グロー、小さな装飾的なはみ出しを生かしつつ、暴走するコンテンツは封じ込められる、ちょうどよい余白です。

.glow-frame {
  overflow: clip;
  overflow-clip-margin: 16px; /* clip 16px outside the padding box */
}
overflow-clip-margin で装飾がクリップを生き延びる

ブラウザサポート

overflow: clip 自体はすべてのブラウザでサポートされています(Baseline 2022)。overflow-clip-margin は Chromium と Firefox でサポートされていますが、Safari ではまだ出荷されていません。プログレッシブエンハンスメントとして扱ってください — クリップ自体は行われ、追加のマージンが失われるだけです。また、現在の実装は両軸が clip に計算される場合にのみこのプロパティを適用します。visible + clip のペアとは組み合わせられません。

クイックリファレンス

シナリオテクニック
本物のスクロール領域(ユーザーまたはプログラムによるスクロール)overflow: auto、または hidden + スクリプトによるスクロール
1行の省略記号overflow: hidden; text-overflow: ellipsis; white-space: nowrap
装飾的なクリッピング — 一切スクロールしないoverflow: clip
ブロック軸をクリップし、インライン軸のシャドウ/アウトラインを保持overflow-x: visible; overflow-y: clip
Grid/Flex トラックのコンテンツが兄弟要素を突き抜けるmin-height: 0(ジオメトリ)+ overflow-y: clip(ペイント)
グローやフォーカスリングをクリップから守るoverflow: clip; overflow-clip-margin: 8px

AIがよくやるミス

  • 望まないオーバーフローすべてに overflow: hidden をデフォルトで使う。 hidden はスクロールコンテナを作ります。フラグメント遷移、scrollIntoView、キーボードフォーカスが静かにスクロールさせ、元に戻すスクロールバーもないままレイアウトがずれます。ペイントを切りたいだけなら clip を使ってください。

  • overflow-y: hidden がもう一方の軸に影響しないと思い込む。 強制変換ルールにより、触れていない visible の軸は auto に計算されます — 宣言した覚えのない水平スクロールコンテナの誕生です。

  • Grid や Flex のコンテンツがトラックからあふれたとき min-height: 0 で止める。 最小サイズのリセットが直すのはトラックのジオメトリだけです。アイテムが overflow-y: clip でクリップするまで、コンテンツは後続の行を突き抜けて描画され続けます。

  • 突き抜けを overflow: hidden で直してシャドウが切断されたまま出荷する。 ショートハンドは両軸をクリップし、インライン軸の box-shadow やアウトラインをパディング端で平らに切断します。overflow-x: visible; overflow-y: clip を使ってください。

  • overflow-clip-marginhidden でも効くと期待する。 このプロパティは clip にのみ適用され、現在の実装では両軸が clip の場合にのみ有効です。

  • clip のボックスがスクロールできると期待する。 clip のボックスはスクロールコンテナではありません。scrollTop への代入は無視され、scrollIntoView はクリップされたコンテンツを表示できません。ボックスを何かがスクロールする必要が少しでもあるなら、autohidden を使ってください。

使い分け

hidden を使う場面

スクロールバーを見せないスクロールコンテナのセマンティクスが必要なとき。1行省略記号のレシピ(overflow: hidden; text-overflow: ellipsis; white-space: nowrap)、scrollTo ベースのスクリプト駆動スクローラー、スクロール可能なコンテンツの計測などです。フォーカスやフラグメント遷移がボックスをスクロールさせうるという副作用は受け入れることになります。

clip を使う場面

ペイントを切りたいだけで、何もスクロールすべきでないとき。装飾のはみ出しの封じ込め、レターボックスステージ、スクロールコンテナが意味を持たない印刷レイアウト、大きすぎる装飾的な背景などです。装飾目的のクリッピングすべてにおいて、clip は副作用がなく低コストなデフォルトです。

visible + clip ペアを使う場面

一方の軸はクリップし、もう一方の軸は本当に visible のままにしたいとき。コンテンツが兄弟の行を突き抜けるアプリシェルのトラックや、インライン軸のシャドウ・グロー・フォーカスアウトラインを完全な形で描画すべき子を持つコンテナです。visible + clip は、強制変換ルールが手を付けない唯一の軸別の組み合わせです。

参考リンク

Revision History

作成更新