1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Frontend CSS – パート12】ブラウザから見た Overflow:要素がはみ出したとき何が起こるのか?

1
Posted at

image.png

ご注意
この記事は AI のサポートを受けていますが


1. 課題:コンテンツが「溢れて」制御不能になるとき

こんな経験、ありませんか?

  • ケース1: カードコンポーネント、長いテキストが枠からはみ出す。overflow: hidden を追加 — 溢れは止まったが、文字が途中で切れて読めない。え、わざと隠してるの?
  • ケース2: モーダル内でコンテンツをスクロールさせたい。.modal-body { overflow: auto; } — コンテンツは長いのに、スクロールバーが… 出てこない。
  • ケース3: ナビバーのドロップダウンメニュー。親に overflow: hidden — メニューが消える。z-index を 9999 にしても無駄。はみ出してるのはメニューなのに、なぜ切れる?
  • ケース4: 商品リスト、長い名前でレイアウトが崩れる。text-overflow: ellipsis を追加 — 三点リーダ(…)が出ない。書いたのに?
  • ケース5: 数百件のメッセージがあるチャット画面。読んでいる最中に新着がロード — 画面がピクッと飛ぶ、読んでいた位置が消える。Scroll Anchoring って何? どうやって止める?

厳しい現実: 多くの人は overflow を「はみ出しを隠すもの」としてしか学びません。hiddenclip の違い、scrollauto の違い、hidden なのに JavaScript でスクロールできる理由 — 全部わからないまま debug すると、当てずっぽうの繰り返しになります。

この記事では overflow の値 と、ブラウザ内部で実際に起きていることを分解していきます。理解すれば、hiddenautoscroll と運任せで試す回数がかなり減るはずです。


2. 本質:Overflow は「切り取り」だけじゃない

CSS では、コンテンツが要素の padding box のサイズを超えたときに overflow(溢れ)が発生します。

でも overflow が答えるのは「はみ出しを隠すか」だけではありません。どう描画するかユーザー(や JavaScript)がどう操作するか — この3つを同時に指示するプロパティです。

overflow の値は 3つの側面 を同時に制御します:

  1. 表示:溢れたコンテンツを見せるか、切るか?
  2. スクロール:ユーザー(や JS)が隠れた部分をスクロールできるか?
  3. レイアウト:新しい Block Formatting Context(BFC)を作るか?

1つでも見落とすと、挙動を予測できません。私も overflow: hidden を指定したのに scrollTop が動いて驚いたことがあります — あのときは2つ目の側面を見落としていました。


3. 各 overflow 値の解剖

overflowoverflow-xoverflow-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 ではスクロール可能scrollTopscrollTo()、アンカーリンクすべて動く。
  • BFC:作る。

overflow: hidden は「スクロール不可」ではありません。スクロールバーを表示しないだけです。JS で隠れたコンテンツを表示範囲に持ってこれます。

使いどころ: カルーセル、モーダル内コンテンツ、スクロールバーを隠しつつコードでスクロール制御したいとき。

3.3. cliphidden に似るが、スクロール完全禁止

比較的新しい値で、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 とは?

hiddenautoscroll はすべて scroll container を作ります — hidden も、スクロールバーが出なくても。

clipvisible は作りません。

scroll container は3つのことをします:

  • 溢れたコンテンツを切り取る
  • 切られた部分をスクロールで見られるようにする(clip を除く)
  • scrollport — 見える領域 — を持つ

見落とされがちな点: overflow: hidden も完全な scroll container です。スクロールバーがない ≠ スクロールできない。これが clip との最大の違いです。

4.3. Block Formatting Context(BFC)

overflowvisibleclip 以外のとき、要素は新しい BFCを作ります。

BFC を作ると:

  • 内部の float を包含
  • 外部の float の侵入を防ぐ
  • margin collapsing を阻止

overflow: clipBFC を作りませんclip を使いつつ BFC が必要? display: flow-root と組み合わせてください。

4.4. Ink overflow vs scrollable overflow

CSS は 2種類の溢れ を区別します:

  1. Ink overflow(インクの溢れ):視覚的に溢れるが box model には参加しない。

    • 例:box-shadowborder-imagetext-decoration、フォントグリフの溢れ、outline
    • スクロール領域は拡張しない
  2. Scrollable overflow(スクロール可能な溢れ):コンテンツ自体がボックスからはみ出す。

    • 例:長いテキスト、大きな画像、子要素
    • スクロール領域を拡張する

この2つを理解すると、box-shadowoverflow: 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-marginoverflow: clip でのみ動作します(hidden では使えません)。

4.5. Overflow はサイズがないと動かない

overflow: auto が「動かない」最も一般的な理由です。

思い込み: overflow: auto を足せばスクロールバーが出る。

実際: ブラウザは scrollport のサイズを知る必要があります — heightmax-height を指定せず、親レイアウトからも制限されない場合、いつコンテンツが「溢れた」か判断できません。

/* ❌ 動かない:高さが固定されていない */
.box {
  overflow: auto;
}

/* ✅ 動く:高さが固定されている */
.box {
  height: 300px;
  overflow: auto;
}

/* ✅ これも動く:レイアウトで制限されている */
.parent {
  height: 500px;
}
.child {
  max-height: 100%; /* 親に制限される */
  overflow: auto;
}

ブラウザは scrollport sizescrollable 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-overflowoverflowvisible 以外で、コンテンツが実際に切られているときだけ動作します。cliphiddenscrollauto すべてで使えます — clip が scroll container でなくても。

5.4. overflow-x / overflow-y の computed value の罠

spec を初めて読んだとき、多くの開発者が驚く技術的な詳細です。

CSS Overflow Module Level 3 によると、overflow-xoverflow-ycomputed value は、指定した値と必ずしも一致しません

一方の軸を visible(デフォルト)、もう一方を scroll / auto / hidden に指定すると、visible の軸はブラウザが auto に変換します。

/* あなたが指定 */
.box {
  overflow-x: visible;
  overflow-y: hidden;
}

/* ブラウザが計算した値 */
.box {
  overflow-x: auto;    /* ← visible が上書きされる! */
  overflow-y: hidden;
}

なぜ? 一方の軸だけスクロール可能で、もう一方が自由に溢れる — という矛盾した挙動は許されません。だから visibleauto に「昇格」させます。

結果: 一方を本当に visible、もう一方を hidden にしたい — overflow-x / overflow-y だけでは不可能です。切りたい軸には clip を使ってください。clipauto に変換されません。

/* 一方 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
  • overflowvisible 以外 → 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: 0flex-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-shadowposition: 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 をいくら上げても無駄です。

解決策:

  1. ドロップダウンを含むコンテナに overflow: hidden を置かない。 角丸が必要なら clip-path、または子要素に直接 border-radius
  2. overflow: hidden が必須? ドロップダウンを外に出す — React Portal で <body> に render するのが一般的。

本番環境で UI ライブラリがやること:

Radix UI、Headless UI、Floating UI(旧 Popper.js)は、だいたい次のどちらかを選びます:

  1. Portaling: ドロップダウンを <body> や最外層コンテナに render — 祖先の overflow 制約から脱出。
  2. 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 — フルコード
ScrollContainer.tsx
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>
  );
};
ScrollContainer.css
.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 を超えたときだけ縦スクロールバーを表示。
  • useEffectscrollTop = scrollHeight は append 型チャット向け。prepend なら scrollTop の保存/復元が必要、または overflow-anchor: none を検討(セクション5.8)。

8. overflow が思い通りにならないときのチェックリスト

overflow が期待どおり動かないとき、順番に確認:

  • サイズは決まっている?heightwidthmax-height、または親レイアウトで制限)→ セクション4.5
  • どの値を使っている? visiblehiddenclipscrollauto の違いは把握できている?
  • JS でスクロールが必要? はい → clip は使わず hidden を。
  • BFC が必要? clip は BFC を作らない → display: flow-root を追加するか hidden を使う。
  • computed value の罠? 一方 visible + 他方 hidden/scroll/autovisibleauto になる。→ セクション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 までレンダリングパイプライン全体に影響します。

クイックまとめ:

  1. overflow は3つを制御:表示、スクロール、BFC。
  2. hidden vs cliphidden は JS スクロール可 + BFC 作成、clip はどちらも不可。
  3. scroll vs autoscroll は scrollbar のスペースを常に確保、auto は必要時のみ。gutter だけ安定させたいなら scrollbar-gutter: stablescroll の代わりに。
  4. 具体的なサイズがないと scrollport は作れない。
  5. computed value の罠visible + 他軸 → visibleauto になる。
  6. Flexbox/Gridoverflow は automatic minimum size に影響 — または min-width: 0 を使う。
  7. hidden + border-radius:dropdown/tooltip バグの温床。
  8. Scroll Anchoring:viewport が飛ぶ原因に — overflow-anchor で制御。
  9. text-overflow: ellipsisclip でも動く(scroll container でなくても)。

これらを押さえれば:

  • コンテンツが溢れるときのレイアウトをより正確に予測できる
  • スクロール問題の debug が速くなる(当てずっぽうが減る)
  • 大きなコンテンツでもパフォーマンスを最適化しやすい
  • 複雑な UI(モーダル、チャット、テーブル)を安定して構築できる

ブラウザの意思決定ツリー

要するに:overflow はブラウザへの命令 — scroll container を作るかBFC を作るかスクロールバーをどう表示するか


参考資料:


👉 次回

【Frontend CSS – パート13】ブラウザから見たレスポンシブレイアウト:なぜMedia Queryだけでは不十分なのか?


1
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?