1
3

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 – パート7】ポジショニングシステムとは?static・relative・absolute・fixed の動作を理解する

1
Posted at

ChatGPT Image Jun 12, 2026, 02_49_53 PM.png

注意
この記事はAIの助けを借りていますが、内容は大規模Webアプリでの実体験に基づいています。


1. 問題:「あれ、マウスポインタどこに飛んでった?」

こういうシチュエーション、めっちゃ心当たりあるんじゃないでしょうか?

  • シチュエーション1: カードの右上に小さな「×」ボタンを置きたい。自信満々で position: absolute; top: 0; right: 0; を書いた……と思ったら、ボタンがブラウザの天辺まで吹っ飛んで、カードだけポツン。なにこれ?
  • シチュエーション2: モーダルポップアップを position: fixed で作って、画面のど真ん中に固定しようとした。なのにスクロールしたらモーダルも一緒にスーッと動いちゃう。「fixed(固定)」のハズじゃなかったの?
  • シチュエーション3: イライラ度MAX。ドロップダウンメニューに z-index: 99999 を吠えたのに、それでも z-index: 1 のヘッダーのに隠れちゃう。なんで?

正直に言ってください: みんな position をなんとなく「おまじない」で覚えてませんか?バグが出たら闇雲に position: relative をばらまいて、z-index を無駄に積み上げて、ダメなら最後の手段 !important で祈るだけ。

そんな曖昧なコーディング、今日で終わりにしましょう。ポジショニングの「手口」をぜんぶ暴露します。もう手探りじゃない、驚きもない!


2. 本質:「通常フロー」と、それを破るやつら

前回の記事(通常フロー)で、普通の要素は自然な「流れ」に沿ってるって話をしました。ブロック要素は縦に、インライン要素は横に並ぶっていう流れです。

でも position は、その流れを思い通りに操るためのパワーをあなたに与えてくれます。

  • static(デフォルト):一番マジメなやつ。常に流れに大人しく従い、決して枠を破らない。
  • relative:二重人格者。肉体はちゃんと流れの中に場所を取ってる(周りの要素は場所を空けてくれる)けど、はちょっと左や右にフワッと動ける。
  • absolute / fixed:自由の戦士たち。正式に通常フローから住所抹消。別次元に飛び立つ。周りの要素は彼らが存在しないかのように振る舞い、自動的に場所を詰める。彼らがどこへ飛ぶかは、「支柱」(包含ブロック)がどこにあるか次第。
  • sticky:へばりつき男。普段は relative みたいにフローに素直にいるけど、スクロールしてあるポイントに達すると、その場に根を張ってピタッと動かなくなる。

この呪文を暗記して:「フローを抜け出す」ってのがカギ。 absolute なブロックが他のテキストをガッツリ覆い被さっても、それはバグじゃなくて仕様。だって別次元に移動したんだから、下の要素たちには見えてないし避けようがない!


3. position 5兄弟の正体を暴く

3.1. static – 透明人間

  • すべてのHTML要素のデフォルト。topleftrightbottomz-index を塗りたくりしても、全部無視。とにかく無害。

3.2. relative – 場所はキープしつつコッソリ移動

  • 元の位置の土地はちゃんと占拠したまま(他の要素は侵入できない)。
  • top/left で好きに動かせる。残した空白はずっとそこに。
  • 最重要秘密: relative を使うときの90%は、自分を動かしたいからじゃなくて、子の absolute がしがみつく「縄張り(包含ブロック)」を作るため。

3.3. absolute – 無軌道な自由人

  • フローから完全に抜け出す。土地は取らない。
  • 包含ブロックを作る最も近い祖先を探す。多くの場合は positionstatic 以外の要素だが、transformfiltercontain などが基準になる場合もある。
  • キラーパターン: 直近の親に position: relative を付け忘れる。すると absolute な子は行き場を失って、最終的にページのルートにへばりつく。そしてあなたの「×」ボタンは空の彼方へ飛んでいく。
.parent {
  /* position: relative を付け忘れた… */
}
.child {
  position: absolute;
  top: 0;
  right: 0;
  /* 初期包含ブロック(通常はビューポート)を基準にしちゃう! */
}

3.4. fixed – 画面に釘付け(ただし誰かに邪魔されなければ)

  • こちらもフローから脱出。
  • ブラウザのウィンドウ(ビューポート)に直接へばりつく。スクロールしまくってもそこに突っ立ってる。ヘッダーや「トップに戻る」ボタンに最適。
  • 落とし穴: ある日、この fixed 要素を transformfiltercontain: paint が付いた親の中に入れたら……ゲームオーバー。その親が勝手に包含ブロックの権利を奪ってしまう。あなたの fixed はスクロールに一緒に引きずられる。まるで悪ふざけみたいに。

3.5. sticky – ずる賢いフェンス

  • fixed と絶対に混同しないで。sticky はもっと賢い。
  • 最初はフローに素直にいる。でもスクロールして指定した位置(例:top: 0)にくると、そこで岩のように固まる
  • ただしこの固まり方は、スクロールコンテナの範囲内でのみ機能する。その境界に達すると、それ以上は移動できなくなる。

4. 包含ブロック – ポジショニング宇宙の「へそ」

ここだ、ここがみんながスルーしがちなラスボス。

簡単に言うと、包含ブロックとは absolutefixedtopleftwidth: 100% を計算するときに使う「見えないボックス」のこと。彼らのサイズはこのボックスに完全に依存する。

どうやってそのボックスを見つけるの?

この宝の地図を見てください:

簡単にまとめると:
absolute を使うなら、基準はたいてい position: relative を持った先祖です。
でも世の中は複雑で、position 以外にも勝手に包含ブロックになりたがるやつらがいる:

  • transform がついた要素
  • filter がついた要素
  • perspective
  • contain: paintcontain: layout
  • will-change: transform

これを押さえれば、ポジショニングに関するバグの90%はニコニコしながら直せます。

topleft% はどう動く?

たった一つのルールを覚えておきましょう:

  • top / bottom = 包含ブロックの高さに従う
  • left / right = 包含ブロックのに従う

例えば top: 10% は、「包含ブロックの高さの10%分だけ上から離れた位置」という意味。絶対にズレたりしない。


5. Z-index と 重ね合わせコンテキスト – 階級闘争

z-index: 9999 を叩き込めば最前面に来ると思ってる?そんなに甘くない。

ようこそ 重ね合わせコンテキスト の世界へ。これは「戸籍制度」みたいなものです。もしあなたの親が z-index: 1 の貧乏カードしか持ってなかったら、自分が z-index: 999999 を積もうが、隣の家の z-index: 2 を持ったやつに軽々踏み潰されます。家の中では親が一番でも、外に出ればルールに従わなきゃいけない。

さらに悪いことに、z-index だけが階級を作るわけじゃない。opacity: 0.99transformfilter が付いた要素も、勝手に新しい重ね合わせコンテキストを生み出し、既存の z-index 秩序をぶち壊します。

血みどろの例:

<div style="position: relative; z-index: 1;">
  <div style="position: absolute; z-index: 9999;">上の人たち、うるさいよ!</div>
</div>
<div style="position: relative; z-index: 2;">俺が上に乗ってやる、文句ある?</div>

見ましたか?子が 9999 で叫んでも、親の z-index: 2 の隣人が平然とその上に鎮座してる。次に z-index: 9999 が効かないからって、さらに 9 を増やす前に、親要素を探しましょう

※重ね合わせコンテキストは深いので、別の記事で詳しく扱います。


6. React で実際に手を動かしてみよう – 百聞は一見に如かず

6.1. モーダル – fixed のアイドル(使い方次第)

超有名ユースケース:画面のど真ん中にポップアップを表示して、背面を暗くしたい。

fixed を使った解決策:
※ 実は現場では ReactDOM.createPortal() でモーダルを <body> 直下にぶち込むことが多いです。親の transformz-index 地獄を避けるため。でも以下は純粋にロジックを理解するためのコードです。

Modal.tsx
import React, { useEffect } from 'react';
import './Modal.css';

interface ModalProps {
  isOpen: boolean;
  onClose: () => void;
  children: React.ReactNode;
}

export const Modal: React.FC<ModalProps> = ({ isOpen, onClose, children }) => {
  // モーダル開いてる間は背面スクロールをロック
  useEffect(() => {
    if (isOpen) {
      document.body.style.overflow = 'hidden';
    } else {
      document.body.style.overflow = '';
    }
    return () => {
      document.body.style.overflow = '';
    };
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-container" onClick={(e) => e.stopPropagation()}>
        <button className="modal-close" onClick={onClose}></button>
        {children}
      </div>
    </div>
  );
};
Modal.css
.modal-overlay {
  position: fixed;    /* こんにちはビューポート、ここにへばりつくよ */
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;      /* 全部の上に */
}

.modal-container {
  background: white;
  border-radius: 8px;
  padding: 20px;
  min-width: 300px;
  max-width: 90%;
  position: relative; /* 閉じるボタン(absolute)のための巣窟 */
}

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

6.2. ドロップダウンメニュー – relativeabsolute を抱きしめる時

どうやってボタンの真下にメニューをピタッと出すか?簡単!

Dropdown.tsx
import React, { useState } from 'react';
import './Dropdown.css';

export const Dropdown: React.FC = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div 
      className="dropdown"
      onMouseEnter={() => setIsOpen(true)}
      onMouseLeave={() => setIsOpen(false)}
    >
      <button className="dropdown-trigger">メニュー ▾</button>
      {isOpen && (
        <ul className="dropdown-menu">
          <li>プロフィール</li>
          <li>設定</li>
          <li>ログアウト</li>
        </ul>
      )}
    </div>
  );
};
Dropdown.css
.dropdown {
  position: relative;   /* 包含ブロックの縄張り宣言 */
  display: inline-block;
}

.dropdown-menu {
  position: absolute;
  top: 100%;            /* 親の高さの100%分だけ下へ(ボタンのすぐ下) */
  left: 0;
  background: white;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 8px 0;
  min-width: 160px;
  z-index: 10;
  list-style: none;
}

.dropdown-menu li {
  padding: 8px 16px;
  cursor: pointer;
}

.dropdown-menu li:hover {
  background: #f0f0f0;
}

6.3. 「トップに戻る」ボタン – fixed の基本形

BackToTop.tsx
import React, { useState, useEffect } from 'react';
import './BackToTop.css';

export const BackToTop: React.FC = () => {
  const [visible, setVisible] = useState(false);

  useEffect(() => {
    const toggleVisibility = () => {
      if (window.scrollY > 300) {
        setVisible(true);
      } else {
        setVisible(false);
      }
    };
    window.addEventListener('scroll', toggleVisibility);
    return () => window.removeEventListener('scroll', toggleVisibility);
  }, []);

  const scrollToTop = () => {
    window.scrollTo({ top: 0, behavior: 'smooth' });
  };

  return (
    <button 
      className="back-to-top" 
      onClick={scrollToTop}
      style={{ display: visible ? 'flex' : 'none' }}
    ></button>
  );
};
BackToTop.css
.back-to-top {
  position: fixed;
  bottom: 20px;
  right: 20px;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: #007bff;
  color: white;
  border: none;
  font-size: 24px;
  cursor: pointer;
  align-items: center;
  justify-content: center;
  z-index: 99;
}

7. チェックリスト:もう position に泣かされないために

「なんで動かないんだ!」と頭をかきむしる前に、この5ステップを唱えてみて:

  • position 書き忘れてない? 大量の z-indextop を書く前に、position: relative/absolute を指定しないと全部無意味。デフォルトは static だよ。
  • 正しい家(包含ブロック)を見つけられた? absolute が迷子になったら、親に relative が付いてるか確認。fixed が画面にへばりつかないなら、先祖に transform などの悪さをしてるやつがいないか探す。
  • z-index リンチに遭ってない? 数字を増やす前に、親要素が低い重ね合わせコンテキストを持ってないかチェック。隣の親より弱いならどうしようもない。
  • width: 100% が予想外にでかくない? absolute な要素の % サイズは、包含ブロックのサイズに従う。外側の直近の親じゃないからね。
  • sticky に過剰な期待してない? stickyfixed じゃない。親要素(スクロールコンテナ)の範囲内でしかへばりつかない。土地がなくなれば一緒に流れる。

8. まとめ

position はブラックマジックじゃありません。必要なのはただ一つの真実を理解することです:通常フローを抜け出したら、自分の基準系(包含ブロック)がどこにあるのかを常に把握しなさい。

その「錨」を掴めば、笑っちゃうようなレイアウトバグは全部解決できます。もうおまじない頼りはやめましょう!

さらに極めるための資料:


次回予告

👉 【Frontend CSS – パート8】Flexboxの内部アルゴリズム – ブラウザはフレックスアイテムをどう計算するのか?

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?