4
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 – パート5】position はブラウザでどう動くのか?Containing Block と座標系を理解する

4
Posted at

ChatGPT Image Jun 3, 2026, 09_37_07 AM.png

注意
この記事はAIの補助を受けて編集しています。

目次


1. 問題: なぜ absolute は意図した場所に配置されないのか?

次のコードを考えてみましょう。

<div class="container">
  <div class="box">
    右上に配置したい
  </div>
</div>
.container {
  width: 400px;
  height: 300px;
  background: #f0f0f0;
  margin: 50px;
}

.box {
  position: absolute;
  top: 0;
  right: 0;
  background: #3498db;
  color: white;
  padding: 10px;
}

多くの人は .box.container の右上に収まると考えるでしょう。しかし実際には、ブラウザウィンドウ(ビューポート)の右上 に表示されてしまいます。

なぜでしょうか? position: absolute は単純に「親要素の上に配置する」わけではありません。Containing Block(包含ブロック) という概念に基づいて位置が決まります。特別な条件を満たす祖先要素がない場合、Containing Block は initial containing block(通常はビューポートと同じサイズ)になります。これが absolute が「どこかに飛んでしまう」最も一般的な原因です。


2. 本質: Containing Block とは何か?

Containing Block (CB) とは、ブラウザが要素の位置やサイズを決める際の基準となる四角形のことです。以下のプロパティに影響します。

  • top, right, bottom, left
  • width, height(パーセント指定時)
  • transform-origin など

Containing Block は、要素自身の position の値や、祖先の特別なプロパティ(transform, filter, perspective など)によって決まります。

Initial Containing Block (ICB) はユーザーエージェントが生成するルートの包含ブロックです。通常はビューポートと同じサイズを持ちます(スクロールバーを除く)。iframe の場合はその iframe のビューポートが ICB になります。実務上は「absolute はビューポートを基準にする」と言いますが、厳密には ICB が基準です。


3. position ごとの Containing Block の決まり方

以下の表は CSS 仕様に基づく Containing Block の決まり方です。

要素の position の値 Containing Block
static, relative 最も近い ブロックコンテナblock, inline-block, flex, grid など)の content box
sticky 通常の Containing Block は relative と同じ。ただし「貼り付き」の状態は nearest scrolling ancestor(後述)が基準。
absolute 以下の条件を満たす 最も近い祖先padding box
- positionstatic 以外(relative, absolute, fixed, sticky
- transform, perspective, filternone 以外
- will-change: transform(多くのモダンブラウザで同様に扱うが注意)
- contain: paint / layout / strict
条件を満たす祖先がない場合は initial containing block
fixed デフォルトはビューポート。ただし transform, perspective, filter を持つ祖先がある場合、その祖先の padding box が CB になる(will-change も同様に影響することが多い)

sticky の補足: sticky には「Containing Block」と「Sticky Scroll Container」という異なる概念があります。詳細は 7節 で説明します。


4. staticrelative – 簡単だが間違いやすい

  • static(デフォルト): 通常フローに従う。top/left/right/bottom は無効。
  • relative: 通常フローに従いつつ、top/left などで 元の位置からの相対移動 ができる。重要なのは、relative(および static 以外のすべての position)は 子孫の absolute 要素に対する Containing Block を作る という点です。
.parent {
  position: relative; /* .child の Containing Block になる */
}
.child {
  position: absolute;
  top: 0;
  left: 0;
}

よくある誤解: position: relative を設定しても、親のボックスモデル(幅・高さ)は変わりません。あくまで「位置決めのコンテキスト」を作るだけです。


5. absolute – 通常フローから外れるが、どこに依存するのか?

position: absolute を指定すると、要素は通常フローから切り離されます(他の要素のレイアウトに影響しません)。位置は Containing Block を基準に top/right/bottom/left で決まります。

Containing Block の探し方:

以下の条件を満たす 最も近い祖先 を見つけます。

  • positionstatic 以外(relative, absolute, fixed, sticky
  • transformnone 以外
  • perspectivenone 以外
  • filternone 以外
  • will-change: transform(多くのモダンブラウザで同様に扱われます。仕様上の厳密な動作ではないため注意が必要です)
  • contain: paint / layout / strict(実際にはあまり使いません)

見つかった祖先があれば、その padding box が Containing Block になります。
見つからなければ initial containing block(≈ ビューポート)になります。

重要: ブラウザは「position の祖先を先に探す」「transform の祖先を次に探す」という順序では探しません。最も近い 祖先を探し、それがどの条件で該当したかは問いません。

<div class="grandparent relative">
  <div class="parent transform">
    <div class="child absolute"></div>
  </div>
</div>

この例では、.parenttransform を持つ)が .child の Containing Block になります。.grandparentrelative ですが距離が遠いため無視されます。

padding box の注意: absolute の Containing Block は padding box です(content box ではありません)。
例: 親が padding: 20px; position: relative; の場合、子の top: 0; left: 0親のパディングの内側(親の外枠から 20px 内側)に配置されます。


6. fixed – 思っているより特殊

position: fixed はヘッダーやフッターを画面固定するのに使われます。デフォルトの Containing Block は ビューポート です。

ただし、祖先に以下のプロパティがあると、Containing Block がその祖先の padding box に変わります。

  • transformnone 以外)
  • perspectivenone 以外)
  • filternone 以外)
  • will-change: transform(多くのブラウザで同様に影響しますが、仕様上はやや曖昧です)
.parent {
  transform: translateZ(0); /* 新しい Containing Block を作る */
}
.child {
  position: fixed;
  bottom: 20px;
  right: 20px;
}

この .child はビューポートに対して固定されず、.parent の中に固定されます。.parent がスクロールすれば一緒に動きます。アニメーションライブラリを使って transform を追加したときに起こりがちなバグです。


7. sticky – スクロールコンテナに貼り付く(Containing Block ではない)

position: sticky はよく誤解されますが、単純に「スクロールコンテナが Containing Block になる」わけではありません。

正しい理解:

  • Containing Blockrelative と同じ(最も近いブロックコンテナの content box)です。これが初期配置と通常フロー内での位置を決めます。
  • しかし「貼り付き」の動作は sticky scroll container(最も近いスクロール機構を持つ祖先)に基づきます。スクロールコンテナとは overflowvisible 以外(auto, scroll, hidden)の要素です。overflow: hidden もスクロールコンテナになります(スクロールバーは表示されないのでバグの原因になりがち)。overflow: visible はスクロールコンテナになりません。

つまり、sticky貼り付く基準位置の基準 が異なるということです。

.scroll-container {
  overflow: auto;   /* これが sticky scroll container */
  height: 300px;
}
.sticky-header {
  position: sticky;
  top: 0;           /* スクロールコンテナの上端に貼り付く */
}

8. React + TypeScript での実践例

8.1. absoluterelative な親に固定する

// RelativeContainer.tsx
import React from 'react';
import './RelativeContainer.css';

export const RelativeContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  return <div className="relative-container">{children}</div>;
};
/* RelativeContainer.css */
.relative-container {
  position: relative;
  width: 300px;
  height: 200px;
  background: #f8f9fa;
  border: 2px solid #ccc;
  margin: 20px;
}
// AbsoluteBox.tsx
import React from 'react';
import './AbsoluteBox.css';

export const AbsoluteBox: React.FC = () => {
  return <div className="absolute-box">relative な親に固定</div>;
};
/* AbsoluteBox.css */
.absolute-box {
  position: absolute;
  top: 10px;
  right: 10px;
  background: #3498db;
  color: white;
  padding: 8px 12px;
  border-radius: 4px;
}

8.2. transformabsolute の固定先を変える例(position: static でも)

// TransformAncestor.tsx
import React from 'react';
import './TransformAncestor.css';

export const TransformAncestor: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  return <div className="transform-ancestor">{children}</div>;
};
/* TransformAncestor.css */
.transform-ancestor {
  transform: translateX(0); /* position: static のまま */
  padding: 20px;
  background: #f0f0f0;
  width: 300px;
  height: 200px;
}
// AbsoluteInside.tsx
import React from 'react';
import './AbsoluteInside.css';

export const AbsoluteInside: React.FC = () => {
  return <div className="absolute-inside">transform な親に固定されてしまう</div>;
};
/* AbsoluteInside.css */
.absolute-inside {
  position: absolute;
  top: 0;
  left: 0;
  background: #e67e22;
  color: white;
  padding: 8px;
}

結果: transform-ancestorposition: static ですが、transform があるため .absolute-inside の Containing Block になります。意図しない位置に配置される典型的なケースです。

8.3. fixedtransform によってビューポート固定ではなくなる例

// TransformParent.tsx
import React from 'react';
import './TransformParent.css';

export const TransformParent: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  return <div className="transform-parent">{children}</div>;
};
/* TransformParent.css */
.transform-parent {
  transform: translateZ(0);
  width: 400px;
  height: 300px;
  overflow: auto;
  background: #f0f0f0;
}
// FixedButton.tsx
import React from 'react';
import './FixedButton.css';

export const FixedButton: React.FC = () => {
  return <button className="fixed-button">固定?</button>;
};
/* FixedButton.css */
.fixed-button {
  position: fixed;
  bottom: 20px;
  right: 20px;
  background: #e74c3c;
  color: white;
  border: none;
  padding: 10px 16px;
  border-radius: 8px;
}

このボタンはビューポートに固定されず、.transform-parent 内に固定されます。アニメーションライブラリで transform を追加した場合によく起こる問題です。

対処法: fixed 要素を transform を持つ要素の外側に出すか、代わりに sticky を検討します。

8.4. sticky ヘッダーの例

// StickyList.tsx
import React from 'react';
import './StickyList.css';

const categories = ['果物', '野菜', '魚介'];

export const StickyList: React.FC = () => {
  return (
    <div className="list-container">
      {categories.map(cat => (
        <div key={cat}>
          <div className="sticky-header">{cat}</div>
          {[...Array(5)].map((_, i) => (
            <div key={i} className="item">{cat} 商品 {i + 1}</div>
          ))}
        </div>
      ))}
    </div>
  );
};
/* StickyList.css */
.list-container {
  height: 300px;
  overflow-y: auto;  /* これがスクロールコンテナになる */
  border: 1px solid #ddd;
}

.sticky-header {
  position: sticky;
  top: 0;
  background: #2c3e50;
  color: white;
  padding: 8px;
  font-weight: bold;
  z-index: 1;
}

.item {
  padding: 8px;
  border-bottom: 1px solid #eee;
}

スクロールすると各ヘッダーが .list-container の上端に貼り付きます。


9. transformfilter のよくある落とし穴

CSS Transforms Specification によると、transformnone 以外の要素は、子孫の absolute および fixed 要素に対して 新しい Containing Block を作ります。これはその要素の positionstatic であっても同じです。

同じように Containing Block を作るプロパティ:

  • transformnone 以外)
  • perspectivenone 以外)
  • filternone 以外)
  • will-change: transform – 多くのモダンブラウザは transform と同様に扱いますが、仕様上は厳密ではないため注意が必要です。
  • contain: paint / layout / strict(実務ではあまり使われません)

結果:

  • position: fixed がビューポートで固定されなくなる。
  • position: absoluteposition: static の祖先に固定されてしまう。

アニメーションライブラリを使うときは特に注意してください。

.parent {
  transform: translateX(0); /* position: static のまま */
}
.child {
  position: absolute;
  top: 0;
  left: 0;
}

この .child.parent を Containing Block として使います。


10. 実践チェックリスト

  • Containing Block を作る祖先はないか?
    (チェック項目: positionstatic, transform, filter, perspective, contain, will-change: transformwill-change は影響する可能性があると覚えておく)
  • absolute を親に固定したいなら、親に position: relative を設定したか?
  • fixed を使っている要素の祖先に transform / filter などがないか? あるとビューポート固定ではなくなる。
  • sticky を使うときは top または bottom を指定したか? スクロールコンテナ(overflowvisible)があるか? overflow: hidden もスクロールコンテナになるがバグの原因になりがち。
  • transform: translate(-50%, -50%) で中央寄せしていないか? これが Containing Block を作ることを忘れていないか?
  • will-change: transform をパフォーマンス改善で使う場合、それが固定レイアウトを壊す可能性を考慮したか? 必要な場面以外では外す。
  • □ DevTools で実際の位置を確認したか?(Computed タブで box model や top/left の値をチェック)

11. まとめ

  • 問題: absolute が意図しない場所に表示されるのは、Containing Block を正しく理解していないから。
  • 本質: Containing Block は位置やサイズの基準となる四角形。position の値や transform などの特別なプロパティによって決まる。
  • ルールの覚え方:
    • absolute最も近い 条件付き祖先(positionstatic または transform/filter など)を探し、その padding box を基準にする。なければ initial containing block(≈ ビューポート)。
    • fixed はデフォルトでビューポート基準。ただし transform などがある祖先の中ではその祖先基準になる。
    • sticky の Containing Block は relative と同じだが、「貼り付き」はスクロールコンテナ(overflowvisible)が基準。
    • relative(および static 以外のすべての position)は absolute 子要素の Containing Block を作れる。
  • 落とし穴: transformfilter は思わぬところで Containing Block を作り、fixedabsolute の動作を変える。アニメーションライブラリを使うときは特に注意。
  • initial containing block は通常ビューポートと同じサイズなので、実務上は「ビューポート基準」と考えて問題ない。

Containing Block の概念を正しく理解すれば、position に関するバグの大半は防げます。


12. 参考資料


次回予告

👉 【Frontend CSS – パート6】Normal Flow(通常フロー)はどう動くのか? ブラウザのデフォルトレイアウトを理解する

4
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
4
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?