<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
This Pen doesn't use any external CSS resources.