<div id="root"></div>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  min-height: 200vh;
  font-family: Arial, sans-serif;
}

/* ヘッダー */
.header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 60px;
  background: white;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  transition: transform 0.3s ease;
  z-index: 1000;
}

.header--hidden {
  transform: translateY(-100%);
}

.header-content {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 20px;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.header-logo {
  font-size: 1.5rem;
  font-weight: bold;
}

.header-nav {
  display: flex;
  gap: 20px;
}

.nav-link {
  color: #333;
  text-decoration: none;
}

.main {
  max-width: 1200px;
  margin: 60px auto 0; /* ヘッダーの高さ分マージン */
  padding: 20px;
}

.card {
  background: #f5f5f5;
  padding: 20px;
  margin-bottom: 20px;
  border-radius: 8px;
  height: 320px;
}

/* Observe 状態のログを右下に固定表示 */
.log-messages {
  position: fixed;
  bottom: 20px;
  right: 20px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 10px 20px;
  border-radius: 5px;
  font-size: 0.9rem;
  z-index: 1001;
  width: 300px;
  max-height: 150px;
  overflow-y: auto;
}

.log-messages ul {
  padding: 0;
  margin: 0;
  list-style-type: none;
}

.log-messages li {
  margin-bottom: 5px;
}
View Compiled
// React の必要なフック (useState, useRef, useEffect) をインポート
const { useState, useRef, useEffect } = React;

const App = () => {
  // ヘッダーが非表示かどうかを管理する状態変数
  const [headerHidden, setHeaderHidden] = useState(false);
  // Observe 状態のログを保持する状態変数
  const [logMessages, setLogMessages] = useState([]);
  // sentinelRef は DOM 要素を参照するための useRef。監視対象となる sentinel 要素を参照
  const sentinelRef = useRef(null);
  // ログメッセージを追加し、3秒後に削除する関数
  const addLogMessage = (message) => {
    // ログメッセージを配列の末尾に追加
    setLogMessages((prevMessages) => [...prevMessages, message]);

    // 3秒後に最初のメッセージを削除
    setTimeout(() => {
      setLogMessages((prevMessages) => prevMessages.slice(1));
    }, 3000); // 3000ms = 3秒
  };

  // コンポーネントがマウントされたときに実行される副作用
  useEffect(() => {
    // IntersectionObserver のコールバック関数。監視対象の交差状態が変化したときに実行
    const observerCallback = (entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          // sentinel がビューポート内に入った場合
          setHeaderHidden(false); // ヘッダーを表示
          addLogMessage("Header Shown"); // ログを追加
        } else {
          // sentinel がビューポート外に出た場合
          setHeaderHidden(true); // ヘッダーを非表示
          addLogMessage("Header Hidden"); // ログを追加
        }
      });
    };

    // IntersectionObserver のオプション設定
    const observerOptions = {
      root: null, // ビューポート全体をルートに設定
      threshold: 0 // 交差が1ピクセル以上発生したときにコールバックを実行
    };

    // IntersectionObserver のインスタンスを作成
    const observer = new IntersectionObserver(
      observerCallback,
      observerOptions
    );

    // sentinel 要素が存在する場合、それを監視対象として設定
    if (sentinelRef.current) {
      observer.observe(sentinelRef.current);
    }

    // コンポーネントがアンマウントされる際にオブザーバーをクリーンアップ
    return () => {
      if (sentinelRef.current) {
        observer.unobserve(sentinelRef.current); // 監視を停止
      }
    };
  }, []); // 空の依存配列により、useEffect は一度だけ実行

  return (
    <div>
      {/* ヘッダー。headerHidden の状態によってクラスを切り替え */}
      <header className={`header ${headerHidden ? "header--hidden" : ""}`}>
        <div className="header-content">
          <div className="header-logo">Logo</div>
          <nav className="header-nav">
            <a href="#" className="nav-link">
              Home
            </a>
            <a href="#" className="nav-link">
              About
            </a>
            <a href="#" className="nav-link">
              Contact
            </a>
          </nav>
        </div>
      </header>

      {/* sentinel 要素。IntersectionObserver による監視対象 */}
      <div id="sentinel" ref={sentinelRef} style={{ height: "1px" }} />

      {/* メインコンテンツ */}
      <main className="main">
        <div className="card">
          <h2>コンテンツセクション1</h2>
          <p>スクロールしてヘッダーの動作を確認してください。</p>
        </div>
        <div className="card">コンテンツカード2</div>
        <div className="card">コンテンツカード3</div>
        <div className="card">コンテンツカード4</div>
      </main>

      {/* Observe 状態のログを右下に表示 */}
      <div className="log-messages">
        <ul>
          {/* ログメッセージをリアルタイムで表示 */}
          {logMessages.map((message, index) => (
            <li key={index}>{message}</li>
          ))}
        </ul>
      </div>
    </div>
  );
};

// Reactのレンダリング = 通常は別ファイル
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js