注意
この記事はAIの補助を受けて編集しています。
目次
- 1. 問題: なぜ
absoluteは意図した場所に配置されないのか? - 2. 本質: Containing Block とは何か?
- 3.
positionごとの Containing Block の決まり方 - 4.
staticとrelative– 簡単だが間違いやすい - 5.
absolute– 通常フローから外れるが、どこに依存するのか? - 6.
fixed– 思っているより特殊 - 7.
sticky– スクロールコンテナに貼り付く(Containing Block ではない) - 8. React + TypeScript での実践例
- 9.
transformとfilterのよくある落とし穴 - 10. 実践チェックリスト
- 11. まとめ
- 12. 参考資料
- 次回予告
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: - position が static 以外(relative, absolute, fixed, sticky)- transform, perspective, filter が none 以外- 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. static と relative – 簡単だが間違いやすい
-
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 の探し方:
以下の条件を満たす 最も近い祖先 を見つけます。
-
positionがstatic以外(relative,absolute,fixed,sticky) -
transformがnone以外 -
perspectiveがnone以外 -
filterがnone以外 -
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>
この例では、.parent(transform を持つ)が .child の Containing Block になります。.grandparent は relative ですが距離が遠いため無視されます。
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 に変わります。
-
transform(none以外) -
perspective(none以外) -
filter(none以外) -
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 Block は
relativeと同じ(最も近いブロックコンテナの content box)です。これが初期配置と通常フロー内での位置を決めます。 - しかし「貼り付き」の動作は sticky scroll container(最も近いスクロール機構を持つ祖先)に基づきます。スクロールコンテナとは
overflowがvisible以外(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. absolute を relative な親に固定する
// 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. transform が absolute の固定先を変える例(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-ancestor は position: static ですが、transform があるため .absolute-inside の Containing Block になります。意図しない位置に配置される典型的なケースです。
8.3. fixed が transform によってビューポート固定ではなくなる例
// 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. transform と filter のよくある落とし穴
CSS Transforms Specification によると、transform が none 以外の要素は、子孫の absolute および fixed 要素に対して 新しい Containing Block を作ります。これはその要素の position が static であっても同じです。
同じように Containing Block を作るプロパティ:
-
transform(none以外) -
perspective(none以外) -
filter(none以外) -
will-change: transform– 多くのモダンブラウザはtransformと同様に扱いますが、仕様上は厳密ではないため注意が必要です。 -
contain: paint / layout / strict(実務ではあまり使われません)
結果:
-
position: fixedがビューポートで固定されなくなる。 -
position: absoluteがposition: staticの祖先に固定されてしまう。
アニメーションライブラリを使うときは特に注意してください。
.parent {
transform: translateX(0); /* position: static のまま */
}
.child {
position: absolute;
top: 0;
left: 0;
}
この .child は .parent を Containing Block として使います。
10. 実践チェックリスト
- □ Containing Block を作る祖先はないか?
(チェック項目:position≠static,transform,filter,perspective,contain,will-change: transform–will-changeは影響する可能性があると覚えておく) - □
absoluteを親に固定したいなら、親にposition: relativeを設定したか? - □
fixedを使っている要素の祖先にtransform/filterなどがないか? あるとビューポート固定ではなくなる。 - □
stickyを使うときはtopまたはbottomを指定したか? スクロールコンテナ(overflow≠visible)があるか?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は 最も近い 条件付き祖先(position≠staticまたはtransform/filterなど)を探し、その padding box を基準にする。なければ initial containing block(≈ ビューポート)。 -
fixedはデフォルトでビューポート基準。ただしtransformなどがある祖先の中ではその祖先基準になる。 -
stickyの Containing Block はrelativeと同じだが、「貼り付き」はスクロールコンテナ(overflow≠visible)が基準。 -
relative(およびstatic以外のすべてのposition)はabsolute子要素の Containing Block を作れる。
-
-
落とし穴:
transformやfilterは思わぬところで Containing Block を作り、fixedやabsoluteの動作を変える。アニメーションライブラリを使うときは特に注意。 - initial containing block は通常ビューポートと同じサイズなので、実務上は「ビューポート基準」と考えて問題ない。
Containing Block の概念を正しく理解すれば、position に関するバグの大半は防げます。
12. 参考資料
- MDN: Containing Block (日本語)
- MDN: Containing Block (英語)
- MDN: position
- CSS Positioned Layout Module Level 3
- CSS Transforms Specification (containing block)
- MDN: will-change
- MDN: contain
- CSS Tricks: Absolute Positioning Inside Relative Positioning
次回予告
👉 【Frontend CSS – パート6】Normal Flow(通常フロー)はどう動くのか? ブラウザのデフォルトレイアウトを理解する
