ご注意
この記事は AI のサポートを受けていますが
1. 課題:コンテンツが「溢れて」制御不能になるとき
こんな経験、ありませんか?
-
ケース1: カードコンポーネント、長いテキストが枠からはみ出す。
overflow: hiddenを追加 — 溢れは止まったが、文字が途中で切れて読めない。え、わざと隠してるの? -
ケース2: モーダル内でコンテンツをスクロールさせたい。
.modal-body { overflow: auto; }— コンテンツは長いのに、スクロールバーが… 出てこない。 -
ケース3: ナビバーのドロップダウンメニュー。親に
overflow: hidden— メニューが消える。z-indexを 9999 にしても無駄。はみ出してるのはメニューなのに、なぜ切れる? -
ケース4: 商品リスト、長い名前でレイアウトが崩れる。
text-overflow: ellipsisを追加 — 三点リーダ(…)が出ない。書いたのに? - ケース5: 数百件のメッセージがあるチャット画面。読んでいる最中に新着がロード — 画面がピクッと飛ぶ、読んでいた位置が消える。Scroll Anchoring って何? どうやって止める?
厳しい現実: 多くの人は overflow を「はみ出しを隠すもの」としてしか学びません。hidden と clip の違い、scroll と auto の違い、hidden なのに JavaScript でスクロールできる理由 — 全部わからないまま debug すると、当てずっぽうの繰り返しになります。
この記事では 各 overflow の値 と、ブラウザ内部で実際に起きていることを分解していきます。理解すれば、hidden → auto → scroll と運任せで試す回数がかなり減るはずです。
2. 本質:Overflow は「切り取り」だけじゃない
CSS では、コンテンツが要素の padding box のサイズを超えたときに overflow(溢れ)が発生します。
でも overflow が答えるのは「はみ出しを隠すか」だけではありません。どう描画するか、ユーザー(や JavaScript)がどう操作するか — この3つを同時に指示するプロパティです。
各 overflow の値は 3つの側面 を同時に制御します:
- 表示:溢れたコンテンツを見せるか、切るか?
- スクロール:ユーザー(や JS)が隠れた部分をスクロールできるか?
- レイアウト:新しい Block Formatting Context(BFC)を作るか?
1つでも見落とすと、挙動を予測できません。私も overflow: hidden を指定したのに scrollTop が動いて驚いたことがあります — あのときは2つ目の側面を見落としていました。
3. 各 overflow 値の解剖
overflow は overflow-x と overflow-y のショートハンドです。値が1つ → 両軸に適用。2つ → 最初が横(overflow-x)、2つ目が縦(overflow-y)。
/* 両軸同じ */
overflow: hidden;
/* 横は hidden、縦は auto */
overflow: hidden auto;
3.1. visible — デフォルト、自然に溢れさせる
デフォルト値 — かつ、ドロップダウンが親から「はみ出す」のに理由がわからないときの犯人でもあります。
- 表示:溢れたコンテンツをそのまま表示、padding box の外に描画される。
- スクロール:scroll container にならない。スクロールバーなし。
- BFC:作らない。
使いどころ: ドロップダウン、ツールチップ、ポップオーバー — 親から「はみ出す」必要があるもの。ただし親に overflow: hidden があれば、子は切られる。親が決める、子じゃない。
3.2. hidden — 隠すが、JS ではスクロールできる
- 表示:溢れたコンテンツは padding box で切られる — 見えないが DOM には残る。
-
スクロール:スクロールバーは出ないが、JavaScript ではスクロール可能 —
scrollTop、scrollTo()、アンカーリンクすべて動く。 - BFC:作る。
overflow: hidden は「スクロール不可」ではありません。スクロールバーを表示しないだけです。JS で隠れたコンテンツを表示範囲に持ってこれます。
使いどころ: カルーセル、モーダル内コンテンツ、スクロールバーを隠しつつコードでスクロール制御したいとき。
3.3. clip — hidden に似るが、スクロール完全禁止
比較的新しい値で、CSS Overflow Module Level 4 で定義。Chrome 90+、Firefox 81+、Safari 16+ でサポート — Can I Use: overflow: clip を参照。
-
表示:
hiddenと同様 — 切り取り。overflow-clip-marginでクリップ領域を拡張可能。 - スクロール:スクロールバーなし、JS スクロールも不可。scroll container ではない。
- BFC:作らない。
hidden vs clip — 見た目は同じ、挙動は違う:
| 項目 | hidden |
clip |
|---|---|---|
| 表示 | 切り取り | 切り取り |
| スクロールバー | 非表示 | 非表示 |
| JS スクロール | 可能 | 不可 |
| BFC 作成 | する | しない |
overflow-clip-margin |
使えない | 使える |
使いどころ: スクロール不要な固定コンテナ — 丸いアバターの切り取り、装飾要素。BFC を作りたくないときに便利。
3.4. scroll — 常にスクロールバーのスペースを確保
- 表示:溢れたコンテンツを切り取る。
- スクロール:常にスクロールバーを表示、コンテンツが溢れていなくても。
- BFC:作る。
overflow: scroll は必要なくてもスクロールバーのスペースを確保 — layout shift 防止に有効。macOS では overlay スクロールバーが常時表示されないこともありますが、layout 上の挙動は auto と異なります。スクロールバーを常時表示せず gutter だけ安定させたいなら scrollbar-gutter: stable を検討(セクション5.7)。
使いどころ: データテーブル、チャットウィンドウ — スクロールバーの出現/消失でレイアウトが跳ねないようにしたいとき。
3.5. auto — 必要なときだけスクロール
- 表示:溢れたコンテンツを切り取る。
- スクロール:コンテンツが実際に溢れたときだけスクロールバーを表示。収まっていればバーなし。
- BFC:作る。
使いどころ: ほとんどのスクロールコンテナ — モーダル body、サイドバー、カード body。scroll との違いは、不要なときにスクロールバーのスペースを「先取り」しないこと。
3.6. クイック比較表
| 値 | 溢れの表示 | スクロールバー | JS スクロール | BFC |
|---|---|---|---|---|
visible |
表示 | 非表示 | ❌ | ❌ |
hidden |
切り取り | 非表示 | ✅ | ✅ |
clip |
切り取り | 非表示 | ❌ | ❌ |
scroll |
切り取り | 常に表示 | ✅ | ✅ |
auto |
切り取り | 必要時のみ | ✅ | ✅ |
4. ブラウザの「内部」における Overflow
overflow を本当に理解するには、レンダリングパイプラインのどこに位置するかを見る必要があります。
4.1. レンダリングパイプラインと overflow の役割
overflow は単なる「描画プロパティ」ではありません。Layout(scroll container 生成時)、Paint(クリップ領域決定時)、Composite(scroll container が独立レイヤーを分離するとき)すべてに関わります。
4.2. Scroll container とは?
hidden、auto、scroll はすべて scroll container を作ります — hidden も、スクロールバーが出なくても。
clip と visible は作りません。
scroll container は3つのことをします:
- 溢れたコンテンツを切り取る
- 切られた部分をスクロールで見られるようにする(
clipを除く) - scrollport — 見える領域 — を持つ
見落とされがちな点: overflow: hidden も完全な scroll container です。スクロールバーがない ≠ スクロールできない。これが clip との最大の違いです。
4.3. Block Formatting Context(BFC)
overflow が visible と clip 以外のとき、要素は新しい BFCを作ります。
BFC を作ると:
- 内部の float を包含
- 外部の float の侵入を防ぐ
- margin collapsing を阻止
overflow: clip は BFC を作りません。clip を使いつつ BFC が必要? display: flow-root と組み合わせてください。
4.4. Ink overflow vs scrollable overflow
CSS は 2種類の溢れ を区別します:
-
Ink overflow(インクの溢れ):視覚的に溢れるが box model には参加しない。
- 例:
box-shadow、border-image、text-decoration、フォントグリフの溢れ、outline - スクロール領域は拡張しない
- 例:
-
Scrollable overflow(スクロール可能な溢れ):コンテンツ自体がボックスからはみ出す。
- 例:長いテキスト、大きな画像、子要素
- スクロール領域を拡張する
この2つを理解すると、box-shadow が overflow: hidden で切られる理由(ink overflow もクリップされる)と、overflow-clip-margin が存在する理由がわかります。
.card {
overflow: clip;
overflow-clip-margin: 8px; /* クリップ領域を拡張 — box-shadow も表示 */
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
overflow-clip-margin は overflow: clip でのみ動作します(hidden では使えません)。
4.5. Overflow はサイズがないと動かない
overflow: auto が「動かない」最も一般的な理由です。
思い込み: overflow: auto を足せばスクロールバーが出る。
実際: ブラウザは scrollport のサイズを知る必要があります — height、max-height を指定せず、親レイアウトからも制限されない場合、いつコンテンツが「溢れた」か判断できません。
/* ❌ 動かない:高さが固定されていない */
.box {
overflow: auto;
}
/* ✅ 動く:高さが固定されている */
.box {
height: 300px;
overflow: auto;
}
/* ✅ これも動く:レイアウトで制限されている */
.parent {
height: 500px;
}
.child {
max-height: 100%; /* 親に制限される */
overflow: auto;
}
ブラウザは scrollport size と scrollable overflow size を比較して、スクロールバーを表示するか決めます。
5. よくある罠(と、あまり知られていない事実)
5.1. hidden は BFC を作る、clip は作らない
この記事で最も見落とされやすい違いです。
/* BFC を作る */
.hidden-container {
overflow: hidden;
}
/* BFC を作らない */
.clip-container {
overflow: clip;
}
clip を使いつつ BFC が必要(float 包含、margin collapse 防止)— レイアウトがずれます。display: flow-root を追加すれば解決。
.clip-container {
overflow: clip;
display: flow-root; /* BFC を強制 */
}
5.2. JavaScript スクロール:hidden は OK、clip は NG
// overflow: hidden の要素 → スクロール可能
const hiddenEl = document.querySelector('.hidden-container');
hiddenEl.scrollTop = 100; // ✅ 動く
hiddenEl.scrollTo(0, 100); // ✅ 動く
// overflow: clip の要素 → スクロール不可
const clipEl = document.querySelector('.clip-container');
clipEl.scrollTop = 100; // ❌ 何も起きない
clipEl.scrollTo(0, 100); // ❌ 何も起きない
仕様どおりの挙動です。 clip はスクロールを完全に無効化 — マウスドラッグ、タッチ、scrollTop の代入、すべて効きません。
5.3. text-overflow: ellipsis には overflow が必要
三点リーダ(…)が欲しいのに、これだけでは出ません:
/* ❌ これだけでは三点リーダが出ない */
.text {
white-space: nowrap;
text-overflow: ellipsis;
}
正しい書き方:
/* ✅ overflow と組み合わせる */
.text {
white-space: nowrap;
overflow: hidden; /* または clip */
text-overflow: ellipsis;
}
text-overflow は overflow が visible 以外で、コンテンツが実際に切られているときだけ動作します。clip、hidden、scroll、auto すべてで使えます — clip が scroll container でなくても。
5.4. overflow-x / overflow-y の computed value の罠
spec を初めて読んだとき、多くの開発者が驚く技術的な詳細です。
CSS Overflow Module Level 3 によると、overflow-x と overflow-y の computed value は、指定した値と必ずしも一致しません。
一方の軸を visible(デフォルト)、もう一方を scroll / auto / hidden に指定すると、visible の軸はブラウザが auto に変換します。
/* あなたが指定 */
.box {
overflow-x: visible;
overflow-y: hidden;
}
/* ブラウザが計算した値 */
.box {
overflow-x: auto; /* ← visible が上書きされる! */
overflow-y: hidden;
}
なぜ? 一方の軸だけスクロール可能で、もう一方が自由に溢れる — という矛盾した挙動は許されません。だから visible を auto に「昇格」させます。
結果: 一方を本当に visible、もう一方を hidden にしたい — overflow-x / overflow-y だけでは不可能です。切りたい軸には clip を使ってください。clip は auto に変換されません。
/* 一方 visible、一方で切り取る方法 */
.box {
overflow-x: visible;
overflow-y: clip; /* hidden の代わりに clip */
}
/* このとき overflow-x は visible のまま */
5.5. Flexbox/Grid と overflow
Flexbox と Grid では、overflow が item の automatic minimum size に影響します。
CSS Flexbox Spec によると、flex item では:
- main axis で
overflow: visible→ automatic minimum size =min-content -
overflowがvisible以外 → automatic minimum size は 0 になり得る
つまり overflow: hidden の flex item は visible のときより小さく縮められる。item が shrink しないときによく使うテクニックです。
.flex-container {
display: flex;
}
.flex-item {
flex: 1;
/* デフォルト: overflow: visible */
/* automatic minimum size = min-content(コンテンツより小さくならない) */
}
.flex-item.shrinkable {
flex: 1;
overflow: hidden; /* automatic minimum size を変更 */
/* コンテンツサイズより小さく縮められる */
}
実践のコツ: shrink させるためだけに overflow を変えるより、min-width: 0(flex-direction: column なら min-height: 0)を使う方が直接的で、コンテンツを切りたくないときの副作用も少ないです。
.flex-item {
min-width: 0; /* コンテンツより小さく縮められる */
/* flex-direction: column なら min-height: 0 */
}
5.6. overflow: hidden + border-radius — 諸刃の剣
親要素に border-radius + overflow: hidden は、画像やコンテンツの角丸に非常によく使われます — しかし同時に tooltip、dropdown、box-shadow、position: absolute の子要素も切ってしまいます。z-index では救えません:クリップは stacking context より先に起きます。
角丸だけ必要なら、親コンテナではなく画像や background に直接 border-radius を指定してください。overflow: hidden が必須なら、Portal でオーバーレイを外に出す — 詳細は セクション6 を参照。
5.7. overflow: overlay(非推奨)と scrollbar-gutter
overflow: overlay は WebKit/Blink の非標準値でした — スクロールバーがレイアウトのスペースを取らずコンテンツの上に重なる描画。現在は deprecated、新規プロジェクトでは使わないでください。
代わりに scrollbar-gutter で、overflow: scroll なしにスクロールバーのスペースを確保できます:
.modal-body {
overflow-y: auto;
scrollbar-gutter: stable; /* gutter を確保 — scrollbar 出現時の layout shift を防ぐ */
}
5.8. Scroll Anchoring — スクロール中に画面が「飛ぶ」理由
Scroll Anchoring は、上側のコンテンツのサイズが変わったとき(画像のロード完了、フォントスワップ、新着メッセージの挿入など)ブラウザが自動でスクロール位置を調整する機能です。目的は良い — 読んでいる箇所が押し出されないようにする。
困るときは? 上方向への infinite scroll、フィードへの新規投稿挿入、lazy-load 画像で上のブロックが高くなる — 読んでいる最中に viewport が不意に飛ぶ。
/* 特定要素の anchoring を無効化 */
.chat-message {
overflow-anchor: none;
}
scroll anchoring の無効化も諸刃の剣:飛ばなくなるが、上のコンテンツが変わったときユーザーが読んでいた位置を失う可能性があります。スクロール位置を自分で制御するときだけ使ってください(例:古いメッセージを prepend するときに scroll position を維持)。
6. 定番ケーススタディ:overflow: hidden でドロップダウンが切れる
誰もが一度は踏む CSS のバグ — そして overflow を理解できているかのテストでもあります。
状況: ナビバーにドロップダウン。角丸にしたくて overflow: hidden を指定 — ドロップダウンが途中で切れる。お馴染みですよね?
<nav class="navbar">
<ul class="menu">
<li class="menu-item">
<button>Products</button>
<ul class="dropdown">
<li>Product A</li>
<li>Product B</li>
</ul>
</li>
</ul>
</nav>
.navbar {
border-radius: 8px;
overflow: hidden; /* ⚠️ 角丸のつもりが、災害に! */
background: #f0f0f0;
}
.menu-item {
position: relative;
}
.dropdown {
position: absolute;
top: 100%;
left: 0;
background: white;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
結果: ドロップダウンがナビバーの border-radius に沿って切られる。
なぜ? overflow: hidden はナビバー内のすべてのコンテンツに クリップ領域を作ります — position: absolute の子も含めて。クリップは z-index と stacking context の処理より先に起きます。z-index をいくら上げても無駄です。
解決策:
-
ドロップダウンを含むコンテナに
overflow: hiddenを置かない。 角丸が必要ならclip-path、または子要素に直接border-radius。 -
overflow: hiddenが必須? ドロップダウンを外に出す — React Portal で<body>に render するのが一般的。
本番環境で UI ライブラリがやること:
Radix UI、Headless UI、Floating UI(旧 Popper.js)は、だいたい次のどちらかを選びます:
-
Portaling: ドロップダウンを
<body>や最外層コンテナに render — 祖先のoverflow制約から脱出。 -
position: fixed:absoluteではなく viewport 基準で配置し、Floating UI で位置を計算 — 中間コンテナによるクリップを回避。
加えて、popover API が段階的にサポートされています — ネイティブのオーバーレイ機構で、近い将来の軽量な選択肢になるかもしれません。
7. 実践:React でシンプルな scroll container
以下は チャットの scroll-to-bottom を模した例:新着メッセージで自動的に最下部へスクロール。上方向への infinite scroll prepend(古いメッセージの読み込み)とは異なり — そちらは scroll anchoring の処理が必要(セクション5.8)。
ScrollContainer.tsx + ScrollContainer.css — フルコード
import React, { useRef, useEffect } from 'react';
import './ScrollContainer.css';
interface ScrollContainerProps {
children: React.ReactNode;
className?: string;
onScrollEnd?: () => void;
}
export const ScrollContainer: React.FC<ScrollContainerProps> = ({
children,
className = '',
onScrollEnd,
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const handleScroll = () => {
if (!containerRef.current || !onScrollEnd) return;
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
// 下端付近(残り10px)に到達したとき
if (scrollHeight - scrollTop <= clientHeight + 10) {
onScrollEnd();
}
};
// children が変わったら自動で最下部へスクロール
useEffect(() => {
if (containerRef.current) {
containerRef.current.scrollTop = containerRef.current.scrollHeight;
}
}, [children]);
return (
<div
ref={containerRef}
className={`scroll-container ${className}`}
onScroll={handleScroll}
>
{children}
</div>
);
};
.scroll-container {
height: 400px; /* ⚠️ 重要:固定の高さが必要 */
overflow-y: auto; /* 必要なときだけ縦スクロール */
overflow-x: hidden; /* 横方向の溢れを隠す */
padding: 1rem;
border: 1px solid #ccc;
border-radius: 8px;
background: #fafafa;
/* スクロールバーのカスタマイズ(Webkit) */
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb {
background: #c1c7cd;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb:hover {
background: #a0a7ae;
}
}
覚えておくこと:
-
height: 400pxが scrollport を作る — 固定サイズがなければoverflow: autoは動かない(セクション4.5)。 -
overflow-y: autoはコンテンツが 400px を超えたときだけ縦スクロールバーを表示。 -
useEffectでscrollTop = scrollHeightは append 型チャット向け。prepend ならscrollTopの保存/復元が必要、またはoverflow-anchor: noneを検討(セクション5.8)。
8. overflow が思い通りにならないときのチェックリスト
overflow が期待どおり動かないとき、順番に確認:
-
サイズは決まっている?(
height、width、max-height、または親レイアウトで制限)→ セクション4.5 -
どの値を使っている?
visible、hidden、clip、scroll、autoの違いは把握できている? -
JS でスクロールが必要? はい →
clipは使わずhiddenを。 -
BFC が必要?
clipは BFC を作らない →display: flow-rootを追加するかhiddenを使う。 -
computed value の罠? 一方
visible+ 他方hidden/scroll/auto→visibleはautoになる。→ セクション5.4 -
text-overflow: ellipsisが出ない?overflow: hidden/clip+white-space: nowrapはある? → セクション5.3 -
Flex/Grid item が縮まない?
overflow: hiddenまたはmin-width: 0を試す。→ セクション5.5 -
スクロール中に画面が飛ぶ? Scroll Anchoring の可能性 —
overflow-anchor: noneを試す。→ セクション5.8 -
box-shadow/outlineが切れる? それは ink overflow —overflow-clip-marginを検討。 -
ドロップダウン/ツールチップが切れる? 祖先に
overflow: hidden+border-radiusがないか確認。→ セクション6
9. まとめと参考資料
overflow は「はみ出しを隠す」だけではありません。ボックスを超えたコンテンツをどう処理するかをブラウザに指示し、Layout から Composite までレンダリングパイプライン全体に影響します。
クイックまとめ:
-
overflowは3つを制御:表示、スクロール、BFC。 -
hiddenvsclip:hiddenは JS スクロール可 + BFC 作成、clipはどちらも不可。 -
scrollvsauto:scrollは scrollbar のスペースを常に確保、autoは必要時のみ。gutter だけ安定させたいならscrollbar-gutter: stableをscrollの代わりに。 - 具体的なサイズがないと scrollport は作れない。
-
computed value の罠:
visible+ 他軸 →visibleはautoになる。 -
Flexbox/Grid:
overflowは automatic minimum size に影響 — またはmin-width: 0を使う。 -
hidden+border-radius:dropdown/tooltip バグの温床。 -
Scroll Anchoring:viewport が飛ぶ原因に —
overflow-anchorで制御。 -
text-overflow: ellipsis:clipでも動く(scroll container でなくても)。
これらを押さえれば:
- コンテンツが溢れるときのレイアウトをより正確に予測できる
- スクロール問題の debug が速くなる(当てずっぽうが減る)
- 大きなコンテンツでもパフォーマンスを最適化しやすい
- 複雑な UI(モーダル、チャット、テーブル)を安定して構築できる
ブラウザの意思決定ツリー
要するに:overflow はブラウザへの命令 — scroll container を作るか、BFC を作るか、スクロールバーをどう表示するか。
参考資料:
- MDN: overflow-x
- MDN: overflow-anchor
- MDN: scrollbar-gutter
- CSS Overflow Module Level 3 Specification
- Can I Use: overflow: clip
- Chrome 90 Blog: overflow: clip
👉 次回
【Frontend CSS – パート13】ブラウザから見たレスポンシブレイアウト:なぜMedia Queryだけでは不十分なのか?
