はじめに

CSSでWebページを構築していると、「要素を特定の位置に固定したい」「要素を重ねて表示したい」「スクロールに追従するヘッダーを作りたい」といった場面に遭遇します。これらの実装には、positionプロパティの理解が不可欠です。

positionプロパティは、要素が文書内でどのように配置されるかを制御するCSSの重要なプロパティです。この仕組みを正しく理解することで、複雑なUIレイアウトを自在に実装できるようになります。

本記事では、positionプロパティについて以下の内容を解説します。

  • 5つのposition値(staticrelativeabsolutefixedsticky)の違い
  • 基準となる包含ブロック(Containing Block)の決定ルール
  • z-indexによる重なり順の制御と重ね合わせコンテキスト
  • 実務でよく使うレイアウトパターン(固定ヘッダー、モーダル、ツールチップなど)

前提条件

本記事を読み進めるにあたり、以下の知識があることを前提としています。

  • HTMLの基本的なタグ構造(divspanheaderなど)
  • CSSの基本構文(セレクタ・プロパティ・値の関係)
  • CSSボックスモデルの基本的な理解

動作確認環境

  • Google Chrome 131以降
  • Firefox 133以降
  • Safari 18以降
  • Microsoft Edge 131以降

positionプロパティとは

MDN Web Docsでは、positionプロパティを次のように説明しています。

positionはCSSのプロパティで、文書内で要素がどのように配置されるかを設定します。toprightbottomleftの各プロパティが、配置された要素の最終的な位置を決定します。

positionプロパティには5つの値があり、それぞれ異なる配置方法を提供します。

説明 通常フロー オフセット適用
static デフォルト値。通常のフローに従って配置 あり なし
relative 通常の位置から相対的にオフセット あり あり
absolute 包含ブロックに対して絶対配置 なし あり
fixed ビューポートに対して固定配置 なし あり
sticky スクロールに応じて相対/固定を切り替え あり あり

位置指定要素とは

positionの値がstatic以外の要素を「位置指定要素(positioned element)」と呼びます。位置指定要素には、toprightbottomleftプロパティでオフセットを指定できます。

1
2
3
4
5
.positioned {
  position: relative; /* static以外なので位置指定要素 */
  top: 20px;          /* オフセットが適用される */
  left: 30px;
}

position: static(デフォルト)

staticpositionプロパティのデフォルト値です。要素は文書の通常フローに従って配置されます。

staticの特性

特性 説明
フロー 通常の文書フローに従う
オフセット toprightbottomleftは無効
z-index 適用されない
包含ブロック 影響しない

staticの使用例

1
2
3
4
5
<div class="container">
  <div class="box static-box">Static Box 1</div>
  <div class="box static-box">Static Box 2</div>
  <div class="box static-box">Static Box 3</div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
.container {
  background: #f0f0f0;
  padding: 20px;
}

.box {
  width: 150px;
  height: 80px;
  margin: 10px 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.static-box {
  position: static; /* デフォルト値なので省略可能 */
  background: #3498db;
  color: white;
  /* top: 20px; は無効 */
}

static要素ではtopleftなどのオフセットプロパティは無視されます。通常のレイアウトでは明示的に指定する必要はありません。

position: relative(相対配置)

relativeは要素を通常の位置から相対的にオフセットさせます。重要な点として、要素が本来占めていた空間は維持されます。

relativeの特性

特性 説明
フロー 通常の文書フローを維持
オフセット基準 要素の本来の位置
元の空間 維持される(他の要素に影響しない)
z-index 適用可能
重ね合わせコンテキスト z-indexauto以外で生成

relativeの動作イメージ

flowchart LR
    subgraph before["オフセット前"]
        A1["Box 1"]
        A2["Box 2"]
        A3["Box 3"]
    end
    
    subgraph after["relative + オフセット後"]
        B1["Box 1"]
        B2["Box 2<br>(移動)"]
        B3["Box 3"]
        B2_space["元の空間<br>(維持)"]
    end
    
    before --> after

relativeの使用例

1
2
3
4
5
<div class="container">
  <div class="box">Box 1</div>
  <div class="box relative-box">Box 2 (relative)</div>
  <div class="box">Box 3</div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
.box {
  width: 100px;
  height: 100px;
  background: #3498db;
  color: white;
  display: inline-block;
  margin: 5px;
  text-align: center;
  line-height: 100px;
}

.relative-box {
  position: relative;
  top: 30px;      /* 本来の位置から下に30px */
  left: 30px;     /* 本来の位置から右に30px */
  background: #e74c3c;
}

上記の例では、Box 2は本来の位置から下に30px、右に30pxオフセットされますが、Box 3の位置には影響しません。

relativeの主な用途

relativeは主に以下の用途で使用します。

  1. absoluteの基準点として使用:子要素をabsoluteで配置する際の包含ブロックを作成
  2. 微調整:要素の位置を微調整したい場合
  3. 重ね合わせコンテキストの生成z-indexを適用するための基盤作り

position: absolute(絶対配置)

absoluteは要素を通常のフローから完全に取り除き、包含ブロックに対して絶対的に配置します。

absoluteの特性

特性 説明
フロー 通常の文書フローから除外
オフセット基準 位置指定された直近の祖先(包含ブロック)
元の空間 維持されない(他の要素が詰める)
内容に合わせて縮小(autoの場合)
z-index 適用可能

包含ブロックの決定ルール

absolute要素の配置基準となる包含ブロックは、以下のルールで決定されます。

flowchart TD
    A["position: absolute の要素"] --> B{"直近の祖先に<br>position: static以外<br>の要素はある?"}
    B -->|はい| C["その祖先の<br>パディングボックスが<br>包含ブロック"]
    B -->|いいえ| D{"祖先にtransform等の<br>プロパティが設定<br>されている?"}
    D -->|はい| E["その祖先の<br>パディングボックスが<br>包含ブロック"]
    D -->|いいえ| F["初期包含ブロック<br>(ビューポート)"]

包含ブロックを形成する主な条件は以下の通りです。

  • positionstatic以外(relativeabsolutefixedsticky
  • transformperspectivefilternone以外
  • containlayoutpaintstrictcontentのいずれか
  • will-changeで上記のプロパティを指定

absoluteの使用例

1
2
3
4
<div class="parent">
  <div class="child absolute-child">Absolute Child</div>
  <p>親要素のコンテンツです。absolute要素はフローから除外されます。</p>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
.parent {
  position: relative;  /* 包含ブロックを形成 */
  width: 400px;
  height: 200px;
  background: #ecf0f1;
  padding: 20px;
}

.absolute-child {
  position: absolute;
  top: 10px;           /* 親のパディングボックス上端から10px */
  right: 10px;         /* 親のパディングボックス右端から10px */
  width: 120px;
  height: 60px;
  background: #e74c3c;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
}

absoluteでの要素サイズ制御

absolute要素では、topbottom、またはleftrightを同時に指定することで、要素のサイズを包含ブロックに基づいて制御できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/* 包含ブロックいっぱいに広げる */
.full-cover {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  /* width: 100%; height: 100%; と同等 */
}

/* 余白を残して広げる */
.with-margin {
  position: absolute;
  top: 20px;
  right: 20px;
  bottom: 20px;
  left: 20px;
}

absoluteによる中央配置

absoluteを使って要素を中央に配置する一般的なパターンです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/* 方法1: transform を使用 */
.center-transform {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

/* 方法2: inset と margin: auto を使用 */
.center-inset {
  position: absolute;
  inset: 0;              /* top, right, bottom, left すべて0 */
  width: 200px;
  height: 100px;
  margin: auto;
}

position: fixed(固定配置)

fixedは要素をビューポートに対して固定配置します。スクロールしても画面上の同じ位置に留まります。

fixedの特性

特性 説明
フロー 通常の文書フローから除外
オフセット基準 ビューポート(通常時)
スクロール ビューポートに固定(追従しない)
重ね合わせコンテキスト 常に生成

fixedの包含ブロックに関する注意

通常、fixed要素の包含ブロックはビューポートですが、祖先要素に以下のプロパティが設定されている場合、その祖先が包含ブロックになります。

  • transformnone以外
  • perspectivenone以外
  • filternone以外
  • contain: paint
  • will-changeで上記のプロパティを指定
1
2
3
4
/* 注意: 親にtransformがあるとfixedが期待通り動作しない */
.parent-with-transform {
  transform: translateZ(0); /* この親の子のfixedは親基準になる */
}

fixedの使用例:固定ヘッダー

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<header class="fixed-header">
  <nav>
    <a href="#home">ホーム</a>
    <a href="#about">概要</a>
    <a href="#contact">お問い合わせ</a>
  </nav>
</header>
<main class="main-content">
  <!-- メインコンテンツ -->
</main>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.fixed-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;             /* 横幅いっぱいに広げる */
  height: 60px;
  background: #2c3e50;
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;        /* 他の要素より前面に */
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.fixed-header nav a {
  color: white;
  text-decoration: none;
  margin: 0 20px;
}

.main-content {
  margin-top: 60px;     /* ヘッダーの高さ分の余白 */
  padding: 20px;
}

fixedの使用例:トップに戻るボタン

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
.back-to-top {
  position: fixed;
  bottom: 30px;
  right: 30px;
  width: 50px;
  height: 50px;
  background: #3498db;
  color: white;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  font-size: 20px;
  z-index: 999;
  transition: opacity 0.3s, transform 0.3s;
}

.back-to-top:hover {
  transform: scale(1.1);
}

position: sticky(粘着配置)

stickyrelativefixedの特性を組み合わせた配置方法です。通常はrelativeのように振る舞い、指定した閾値に達するとfixedのように振る舞います。

stickyの特性

特性 説明
フロー 通常の文書フローを維持
オフセット基準 直近のスクロールする祖先
閾値 topbottom等で指定
重ね合わせコンテキスト 常に生成

stickyの動作条件

stickyが正しく動作するためには、以下の条件を満たす必要があります。

  1. 閾値の指定toprightbottomleftのうち少なくとも1つを指定
  2. 親の高さ:親要素に十分な高さがある
  3. overflow:祖先のoverflowvisibleである(hiddenscrollautoだと動作しない場合がある)

stickyの動作イメージ

flowchart TD
    subgraph scroll["スクロール状態による動作"]
        A["閾値に達していない"] --> |"relative として動作"| B["通常のフローで配置"]
        C["閾値に達した"] --> |"fixed として動作"| D["スクロール祖先に固定"]
        E["親の下端に達した"] --> |"スクロールアウト"| F["親と共にスクロール"]
    end

stickyの使用例:粘着ヘッダー

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<div class="scroll-container">
  <section>
    <h2 class="sticky-header">セクション A</h2>
    <div class="content">
      <p>セクションAのコンテンツです。</p>
      <p>スクロールすると見出しが固定されます。</p>
    </div>
  </section>
  <section>
    <h2 class="sticky-header">セクション B</h2>
    <div class="content">
      <p>セクションBのコンテンツです。</p>
    </div>
  </section>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.scroll-container {
  height: 400px;
  overflow-y: auto;
}

section {
  min-height: 300px;
}

.sticky-header {
  position: sticky;
  top: 0;              /* 閾値: 上端から0pxで固定 */
  background: #3498db;
  color: white;
  padding: 15px 20px;
  margin: 0;
  z-index: 10;
}

.content {
  padding: 20px;
  background: #ecf0f1;
}

stickyの使用例:サイドバーナビゲーション

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
.sidebar {
  width: 250px;
  float: left;
}

.sidebar-nav {
  position: sticky;
  top: 80px;           /* 固定ヘッダーの下に配置 */
  max-height: calc(100vh - 100px);
  overflow-y: auto;
}

.main-article {
  margin-left: 270px;
}

z-indexと重ね合わせコンテキスト

位置指定要素の重なり順を制御するには、z-indexプロパティを使用します。

z-indexの基本

z-indexは位置指定要素(positionstatic以外)に対してのみ有効です。値が大きいほど前面に表示されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
.box-back {
  position: relative;
  z-index: 1;         /* 背面 */
  background: #3498db;
}

.box-front {
  position: relative;
  z-index: 2;         /* 前面 */
  background: #e74c3c;
}

重ね合わせコンテキスト(Stacking Context)

重ね合わせコンテキストは、z-indexの比較が行われる独立した空間です。異なる重ね合わせコンテキストに属する要素のz-indexは直接比較されません。

flowchart TB
    subgraph root["ルート重ね合わせコンテキスト"]
        A["要素A<br>z-index: 1"]
        subgraph context1["コンテキスト1 (z-index: 2)"]
            B["要素B<br>z-index: 100"]
        end
        subgraph context2["コンテキスト2 (z-index: 3)"]
            C["要素C<br>z-index: 1"]
        end
    end

上記の例では、要素Bのz-indexが100でも、コンテキスト1全体のz-indexが2なので、コンテキスト2の要素C(z-index: 1)より背面に表示されます。

重ね合わせコンテキストを生成する条件

以下の条件で新しい重ね合わせコンテキストが生成されます。

条件
ルート要素(<html> -
positionrelative/absolutez-indexauto以外 position: relative; z-index: 1;
position: fixed position: fixed;
position: sticky position: sticky; top: 0;
opacityが1未満 opacity: 0.9;
transformnone以外 transform: scale(1);
filternone以外 filter: blur(0);
isolation: isolate isolation: isolate;

z-indexのベストプラクティス

z-indexの管理を容易にするため、以下のプラクティスを推奨します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
:root {
  /* z-indexスケールを定義 */
  --z-dropdown: 100;
  --z-sticky: 200;
  --z-fixed: 300;
  --z-modal-backdrop: 400;
  --z-modal: 500;
  --z-tooltip: 600;
  --z-toast: 700;
}

.dropdown-menu {
  z-index: var(--z-dropdown);
}

.sticky-header {
  z-index: var(--z-sticky);
}

.modal-backdrop {
  z-index: var(--z-modal-backdrop);
}

.modal {
  z-index: var(--z-modal);
}

実践的なレイアウトパターン

パターン1: モーダルダイアログ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<div class="modal-backdrop">
  <div class="modal">
    <div class="modal-header">
      <h2>確認</h2>
      <button class="modal-close">&times;</button>
    </div>
    <div class="modal-body">
      <p>この操作を実行しますか?</p>
    </div>
    <div class="modal-footer">
      <button class="btn btn-secondary">キャンセル</button>
      <button class="btn btn-primary">実行</button>
    </div>
  </div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
.modal-backdrop {
  position: fixed;
  inset: 0;                    /* top, right, bottom, left すべて0 */
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.modal {
  position: relative;          /* 内部のabsolute要素の基準 */
  width: 90%;
  max-width: 500px;
  background: white;
  border-radius: 8px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
  overflow: hidden;
}

.modal-close {
  position: absolute;
  top: 10px;
  right: 10px;
  background: none;
  border: none;
  font-size: 24px;
  cursor: pointer;
  color: #666;
}

.modal-header {
  padding: 20px;
  border-bottom: 1px solid #eee;
}

.modal-body {
  padding: 20px;
}

.modal-footer {
  padding: 15px 20px;
  border-top: 1px solid #eee;
  display: flex;
  justify-content: flex-end;
  gap: 10px;
}

パターン2: ツールチップ

1
2
3
4
5
6
<div class="tooltip-container">
  <button class="tooltip-trigger">ヘルプ</button>
  <div class="tooltip">
    ここに説明テキストが表示されます。
  </div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
.tooltip-container {
  position: relative;
  display: inline-block;
}

.tooltip {
  position: absolute;
  bottom: 100%;              /* トリガーの上に配置 */
  left: 50%;
  transform: translateX(-50%);
  padding: 8px 12px;
  background: #2c3e50;
  color: white;
  font-size: 14px;
  border-radius: 4px;
  white-space: nowrap;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s, visibility 0.2s;
  z-index: 100;
  margin-bottom: 8px;
}

/* 矢印 */
.tooltip::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: #2c3e50;
}

.tooltip-container:hover .tooltip {
  opacity: 1;
  visibility: visible;
}

パターン3: カード内のバッジ

1
2
3
4
5
6
7
8
<div class="card">
  <span class="badge">NEW</span>
  <img src="product.jpg" alt="商品画像" class="card-image">
  <div class="card-content">
    <h3>商品名</h3>
    <p>商品の説明文</p>
  </div>
</div>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
.card {
  position: relative;          /* バッジの包含ブロック */
  width: 300px;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.badge {
  position: absolute;
  top: 10px;
  right: 10px;
  padding: 4px 12px;
  background: #e74c3c;
  color: white;
  font-size: 12px;
  font-weight: bold;
  border-radius: 4px;
  z-index: 1;
}

.card-image {
  width: 100%;
  height: 200px;
  object-fit: cover;
}

.card-content {
  padding: 15px;
}

パターン4: スティッキーテーブルヘッダー

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.table-container {
  max-height: 400px;
  overflow-y: auto;
}

table {
  width: 100%;
  border-collapse: collapse;
}

thead th {
  position: sticky;
  top: 0;
  background: #3498db;
  color: white;
  padding: 12px 15px;
  text-align: left;
  z-index: 1;
}

tbody td {
  padding: 10px 15px;
  border-bottom: 1px solid #eee;
}

よくある問題と解決策

問題1: absoluteの位置がずれる

原因: 包含ブロックとなる祖先要素が存在しない

解決策: 親要素にposition: relativeを設定

1
2
3
4
5
6
7
8
9
/* Before: absoluteがビューポート基準になってしまう */
.parent {
  /* position未指定 */
}

/* After: 親が包含ブロックになる */
.parent {
  position: relative;
}

問題2: fixedがビューポートに固定されない

原因: 祖先要素にtransformfilterが設定されている

解決策: 該当のプロパティを削除するか、要素の構造を変更

1
2
3
4
5
6
/* 問題のあるケース */
.ancestor {
  transform: translateZ(0); /* これがfixedの動作を変える */
}

/* 解決策: transformを使わないか、fixed要素を外に出す */

問題3: stickyが効かない

原因: 親要素にoverflow: hiddenが設定されている、または閾値が未指定

解決策:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/* Before */
.parent {
  overflow: hidden; /* stickyを無効化 */
}

.sticky-element {
  position: sticky;
  /* top未指定 */
}

/* After */
.parent {
  overflow: visible;
}

.sticky-element {
  position: sticky;
  top: 0; /* 閾値を指定 */
}

問題4: z-indexが効かない

原因: 要素が位置指定されていない、または重ね合わせコンテキストの影響

解決策: positionを設定し、重ね合わせコンテキストを確認

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/* Before: staticでは z-index無効 */
.element {
  z-index: 100; /* 無効 */
}

/* After */
.element {
  position: relative;
  z-index: 100; /* 有効 */
}

まとめ

positionプロパティの理解は、複雑なUIレイアウトを実装する上で不可欠です。本記事で解説した内容を整理すると以下のようになります。

主な用途 ポイント
static デフォルト配置 オフセット・z-index無効
relative 微調整、absoluteの基準 元の空間を維持
absolute 自由配置、モーダル 包含ブロックを意識
fixed 固定ヘッダー、フローティングボタン transformに注意
sticky スティッキーヘッダー 閾値とoverflow確認

z-indexと重ね合わせコンテキストの概念を理解することで、要素の重なり順を正確に制御できます。実践では、CSS変数を使ったz-indexスケールの管理を推奨します。

参考リンク