下スクロールで隠れるヘッダー
下方向スクロールでヘッダーを隠し、上方向に戻すと即座に再表示。読書領域を最大化しつつ操作性を保つ手法で、記事・ドキュメント・モバイルサイトに向きます。スクロール方向の差分で translateY を制御します。
ライブデモ
使用例(お題: カフェ MOON BREW)
この技法を「カフェ MOON BREW」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- MOON BREW:読み物ページで下スクロール時にヘッダーを隠す -->
<div class="hos-frame">
<header class="hos-head" id="hosHead">
<a class="hos-logo" href="#" onclick="return false">☕ MOON BREW</a>
<nav class="hos-nav">
<a href="#" onclick="return false">メニュー</a>
<a href="#" onclick="return false">物語</a>
<a href="#" onclick="return false">店舗</a>
<a href="#" onclick="return false">通販</a>
</nav>
</header>
<div class="hos-scroll" id="hosScroll">
<article class="hos-article">
<h1>一杯のために、夜を焙煎する</h1>
<p class="hos-lead">深夜の焙煎室から。MOON BREW の豆ができるまでの物語を、ゆっくりお届けします。読むあいだ、ヘッダーはそっと身を引きます。</p>
<p>下方向に読み進めるとナビは隠れ、上に少し戻すと現れます。長い読み物でも本文に没入してもらえます。</p>
<p>エチオピア・イルガチェフェの浅煎りは、ベリーのような明るい酸味。冷めていく過程で表情が変わるのも魅力です。</p>
<p>抽出は92℃、30秒の蒸らしから。豆の状態に合わせて、毎朝レシピを微調整しています。</p>
<p>このページは自動で下→上にスクロールし、ヘッダーの隠れる/現れるを実演します。</p>
<p>次の章では、店舗での提供温度とカップの選び方についてお話しします。</p>
</article>
</div>
</div>
CSS
/* MOON BREW(カフェ):下スクロールで隠れるヘッダーの再スキン */
* { box-sizing: border-box; }
body { margin: 0; font-family: "Hiragino Mincho ProN", "Segoe UI", serif; }
.hos-frame { position: relative; width: 100%; height: 380px; overflow: hidden; background: #f6f1e7; }
.hos-head {
position: absolute; top: 0; left: 0; right: 0; z-index: 10;
display: flex; align-items: center; gap: 18px; height: 56px; padding: 0 22px;
background: #fffdf8; border-bottom: 1px solid #ece2d0;
box-shadow: 0 4px 16px rgba(80,54,30,.07);
transform: translateY(0); transition: transform .35s cubic-bezier(.4,0,.2,1);
}
.hos-head.is-hidden { transform: translateY(-100%); }
.hos-logo { font-family: "Segoe UI", system-ui, sans-serif; font-size: 17px; font-weight: 800; color: #7a4f2a; text-decoration: none; letter-spacing: .02em; }
.hos-nav { display: flex; gap: 4px; margin-left: auto; }
.hos-nav a { font-family: "Segoe UI", system-ui, sans-serif; color: #5b4630; text-decoration: none; font-size: 13px; font-weight: 600; padding: 7px 12px; border-radius: 8px; transition: background .2s ease, color .2s ease; }
.hos-nav a:hover { background: #f1e7d6; color: #7a4f2a; }
.hos-scroll { height: 100%; overflow-y: auto; scrollbar-width: thin; padding-top: 56px; }
.hos-article { max-width: 600px; margin: 0 auto; padding: 28px 26px 90px; color: #3c2f22; line-height: 1.95; }
.hos-article h1 { font-size: 27px; font-weight: 800; margin: 6px 0 16px; letter-spacing: .01em; }
.hos-lead {
font-family: "Segoe UI", system-ui, sans-serif; font-size: 14.5px; color: #6a5742;
padding: 14px 18px; margin: 0 0 22px; border-left: 4px solid #b07a3f;
background: rgba(176,122,63,.08); border-radius: 0 10px 10px 0;
}
.hos-article p { margin: 0 0 18px; }
@media (prefers-reduced-motion: reduce) { .hos-head { transition: none; } }
JavaScript
// (デモと同じフックを流用)スクロール方向でヘッダーを隠す/見せる
(() => {
const sc = document.getElementById('hosScroll');
const head = document.getElementById('hosHead');
if (!sc || !head) return;
let last = 0; const DELTA = 4, TOP_LOCK = 60;
function apply() {
const y = sc.scrollTop;
if (y < TOP_LOCK) head.classList.remove('is-hidden');
else if (y - last > DELTA) head.classList.add('is-hidden');
else if (last - y > DELTA) head.classList.remove('is-hidden');
last = y;
}
let ticking = false;
sc.addEventListener('scroll', () => {
if (ticking) return; ticking = true;
requestAnimationFrame(() => { apply(); ticking = false; });
}, { passive: true });
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
let auto = !reduce, dir = 1;
['wheel', 'touchstart', 'pointerdown'].forEach(ev => sc.addEventListener(ev, () => { auto = false; }, { passive: true }));
if (auto) {
setTimeout(function step() {
if (!auto) return;
const max = sc.scrollHeight - sc.clientHeight;
sc.scrollTop += dir * 2.2;
if (sc.scrollTop >= Math.min(max, 240)) dir = -1; else if (sc.scrollTop <= 0) dir = 1;
requestAnimationFrame(step);
}, 1000);
}
})();
実装ガイド
使いどころ
記事・ドキュメント・モバイルの縦長ページなど、本文の表示面積を最大化したいページに。下方向スクロールでヘッダーを隠し、上方向に少しでも戻すと即座に再表示します。
実装時の注意点
直前の scrollTop との差分でスクロール方向を判定し、translateY(-100%) を付け外しするだけの軽量実装です。小刻みな揺れを無視する DELTA と、最上部では必ず表示する TOP_LOCK でチラつきを抑えています。
対応ブラウザ
transform アニメーションと Scroll イベントは全モダンブラウザで安定動作します。passive リスナーでスクロール性能を阻害しません。特別なフォールバックは不要です。
よくある失敗
差分判定にしきい値を設けないと、慣性スクロールの微振動でヘッダーが点滅します。最上部付近で隠したままになると到達不能になるため TOP_LOCK は必須。固定要素なので、本文側に同じ高さの padding-top を必ず確保してください。
応用例
隠す代わりに半透明+縮小に変える、上スクロール時だけ検索バーを出す、一定速度以上の素早いスワイプでのみ反応させる、といった調整が可能です。
コード
HTML
<!-- 下スクロールで隠れ、上スクロールで戻るヘッダー -->
<div class="hos-frame">
<header class="hos-head" id="hosHead">
<a class="hos-logo" href="#" onclick="return false">◢ Lumen</a>
<nav class="hos-nav">
<a href="#" onclick="return false">ホーム</a>
<a href="#" onclick="return false">特集</a>
<a href="#" onclick="return false">連載</a>
<a href="#" onclick="return false">購読</a>
</nav>
</header>
<div class="hos-scroll" id="hosScroll">
<article class="hos-article">
<h1>読むときは消え、戻すと現れる</h1>
<p class="hos-lead">下方向のスクロールではヘッダーを隠して本文に集中させ、上方向に少しでも戻すと即座に再表示します。スクロール方向の差分を見て translateY を切り替えるだけの軽量実装です。</p>
<p>ニュース記事やドキュメント、モバイルの縦長ページで特に有効。常時固定だと狭い画面で本文を圧迫しますが、この方式なら必要な瞬間だけナビが戻ってきます。</p>
<p>判定はシンプル。直前の scrollTop と比較して、増えていれば隠し、減っていれば見せる。最上部付近では必ず表示してチラつきを防ぎます。</p>
<p>閾値とアニメーション速度はCSSとJSの定数だけで調整可能。プロジェクトのトーンに合わせて軽快にも上品にもできます。</p>
<p>このデモは一度だけ自動で下→上へスクロールし、隠れる/現れるの両方を見せます。ホイールやタッチで触れると自動は停止します。</p>
<p>最後の段落。スクロール方向ベースのヘッダー制御は、固定ヘッダーの「邪魔さ」を消しつつ到達性を残す折衷案として定番です。</p>
</article>
</div>
</div>
CSS
* { box-sizing: border-box; }
body { margin: 0; font-family: "Segoe UI", system-ui, -apple-system, sans-serif; }
.hos-frame { position: relative; width: 100%; height: 380px; overflow: hidden; background: #f6f7fb; }
.hos-head {
position: absolute;
top: 0; left: 0; right: 0;
z-index: 10;
display: flex; align-items: center; gap: 18px;
height: 56px; padding: 0 22px;
background: #ffffff;
border-bottom: 1px solid #eceef3;
box-shadow: 0 4px 16px rgba(20,24,40,.06);
transform: translateY(0);
transition: transform .35s cubic-bezier(.4, 0, .2, 1);
}
.hos-head.is-hidden { transform: translateY(-100%); }
.hos-logo { font-size: 18px; font-weight: 800; color: #4f46e5; text-decoration: none; letter-spacing: .02em; }
.hos-nav { display: flex; gap: 4px; margin-left: auto; }
.hos-nav a {
color: #353a45; text-decoration: none;
font-size: 13px; font-weight: 600;
padding: 7px 12px; border-radius: 8px;
transition: background .2s ease, color .2s ease;
}
.hos-nav a:hover { background: #eef0fb; color: #4f46e5; }
.hos-scroll { height: 100%; overflow-y: auto; scrollbar-width: thin; padding-top: 56px; }
.hos-article { max-width: 600px; margin: 0 auto; padding: 28px 26px 90px; color: #2a2e38; line-height: 1.85; }
.hos-article h1 { font-size: 26px; font-weight: 800; margin: 6px 0 16px; letter-spacing: -.01em; }
.hos-lead {
font-size: 15px; color: #515767;
padding: 14px 18px; margin: 0 0 22px;
border-left: 4px solid #6366f1;
background: rgba(99,102,241,.06);
border-radius: 0 10px 10px 0;
}
.hos-article p { margin: 0 0 18px; }
@media (prefers-reduced-motion: reduce) { .hos-head { transition: none; } }
JavaScript
// スクロール方向の差分でヘッダーを隠す/見せる
(() => {
const sc = document.getElementById('hosScroll');
const head = document.getElementById('hosHead');
if (!sc || !head) return;
let last = 0;
const DELTA = 4; // 小刻みな揺れを無視する閾値
const TOP_LOCK = 60; // 最上部付近では常に表示
function apply() {
const y = sc.scrollTop;
if (y < TOP_LOCK) {
head.classList.remove('is-hidden');
} else if (y - last > DELTA) {
head.classList.add('is-hidden'); // 下方向 → 隠す
} else if (last - y > DELTA) {
head.classList.remove('is-hidden'); // 上方向 → 見せる
}
last = y;
}
let ticking = false;
sc.addEventListener('scroll', () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => { apply(); ticking = false; });
}, { passive: true });
// 一度だけ下→上へ自動スクロールし、隠れる→現れるを実演
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
let auto = !reduce, dir = 1;
['wheel', 'touchstart', 'pointerdown'].forEach(ev =>
sc.addEventListener(ev, () => { auto = false; }, { passive: true }));
if (auto) {
setTimeout(function step() {
if (!auto) return;
const max = sc.scrollHeight - sc.clientHeight;
sc.scrollTop += dir * 2.2;
if (sc.scrollTop >= Math.min(max, 240)) dir = -1;
else if (sc.scrollTop <= 0) dir = 1;
requestAnimationFrame(step);
}, 1000);
}
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「下スクロールで隠れるヘッダー」の効果を追加してください。
# 追加してほしい効果
下スクロールで隠れるヘッダー(ヘッダー)
下方向スクロールでヘッダーを隠し、上方向に戻すと即座に再表示。読書領域を最大化しつつ操作性を保つ手法で、記事・ドキュメント・モバイルサイトに向きます。スクロール方向の差分で translateY を制御します。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 下スクロールで隠れ、上スクロールで戻るヘッダー -->
<div class="hos-frame">
<header class="hos-head" id="hosHead">
<a class="hos-logo" href="#" onclick="return false">◢ Lumen</a>
<nav class="hos-nav">
<a href="#" onclick="return false">ホーム</a>
<a href="#" onclick="return false">特集</a>
<a href="#" onclick="return false">連載</a>
<a href="#" onclick="return false">購読</a>
</nav>
</header>
<div class="hos-scroll" id="hosScroll">
<article class="hos-article">
<h1>読むときは消え、戻すと現れる</h1>
<p class="hos-lead">下方向のスクロールではヘッダーを隠して本文に集中させ、上方向に少しでも戻すと即座に再表示します。スクロール方向の差分を見て translateY を切り替えるだけの軽量実装です。</p>
<p>ニュース記事やドキュメント、モバイルの縦長ページで特に有効。常時固定だと狭い画面で本文を圧迫しますが、この方式なら必要な瞬間だけナビが戻ってきます。</p>
<p>判定はシンプル。直前の scrollTop と比較して、増えていれば隠し、減っていれば見せる。最上部付近では必ず表示してチラつきを防ぎます。</p>
<p>閾値とアニメーション速度はCSSとJSの定数だけで調整可能。プロジェクトのトーンに合わせて軽快にも上品にもできます。</p>
<p>このデモは一度だけ自動で下→上へスクロールし、隠れる/現れるの両方を見せます。ホイールやタッチで触れると自動は停止します。</p>
<p>最後の段落。スクロール方向ベースのヘッダー制御は、固定ヘッダーの「邪魔さ」を消しつつ到達性を残す折衷案として定番です。</p>
</article>
</div>
</div>
【CSS】
* { box-sizing: border-box; }
body { margin: 0; font-family: "Segoe UI", system-ui, -apple-system, sans-serif; }
.hos-frame { position: relative; width: 100%; height: 380px; overflow: hidden; background: #f6f7fb; }
.hos-head {
position: absolute;
top: 0; left: 0; right: 0;
z-index: 10;
display: flex; align-items: center; gap: 18px;
height: 56px; padding: 0 22px;
background: #ffffff;
border-bottom: 1px solid #eceef3;
box-shadow: 0 4px 16px rgba(20,24,40,.06);
transform: translateY(0);
transition: transform .35s cubic-bezier(.4, 0, .2, 1);
}
.hos-head.is-hidden { transform: translateY(-100%); }
.hos-logo { font-size: 18px; font-weight: 800; color: #4f46e5; text-decoration: none; letter-spacing: .02em; }
.hos-nav { display: flex; gap: 4px; margin-left: auto; }
.hos-nav a {
color: #353a45; text-decoration: none;
font-size: 13px; font-weight: 600;
padding: 7px 12px; border-radius: 8px;
transition: background .2s ease, color .2s ease;
}
.hos-nav a:hover { background: #eef0fb; color: #4f46e5; }
.hos-scroll { height: 100%; overflow-y: auto; scrollbar-width: thin; padding-top: 56px; }
.hos-article { max-width: 600px; margin: 0 auto; padding: 28px 26px 90px; color: #2a2e38; line-height: 1.85; }
.hos-article h1 { font-size: 26px; font-weight: 800; margin: 6px 0 16px; letter-spacing: -.01em; }
.hos-lead {
font-size: 15px; color: #515767;
padding: 14px 18px; margin: 0 0 22px;
border-left: 4px solid #6366f1;
background: rgba(99,102,241,.06);
border-radius: 0 10px 10px 0;
}
.hos-article p { margin: 0 0 18px; }
@media (prefers-reduced-motion: reduce) { .hos-head { transition: none; } }
【JavaScript】
// スクロール方向の差分でヘッダーを隠す/見せる
(() => {
const sc = document.getElementById('hosScroll');
const head = document.getElementById('hosHead');
if (!sc || !head) return;
let last = 0;
const DELTA = 4; // 小刻みな揺れを無視する閾値
const TOP_LOCK = 60; // 最上部付近では常に表示
function apply() {
const y = sc.scrollTop;
if (y < TOP_LOCK) {
head.classList.remove('is-hidden');
} else if (y - last > DELTA) {
head.classList.add('is-hidden'); // 下方向 → 隠す
} else if (last - y > DELTA) {
head.classList.remove('is-hidden'); // 上方向 → 見せる
}
last = y;
}
let ticking = false;
sc.addEventListener('scroll', () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => { apply(); ticking = false; });
}, { passive: true });
// 一度だけ下→上へ自動スクロールし、隠れる→現れるを実演
const reduce = matchMedia('(prefers-reduced-motion: reduce)').matches;
let auto = !reduce, dir = 1;
['wheel', 'touchstart', 'pointerdown'].forEach(ev =>
sc.addEventListener(ev, () => { auto = false; }, { passive: true }));
if (auto) {
setTimeout(function step() {
if (!auto) return;
const max = sc.scrollHeight - sc.clientHeight;
sc.scrollTop += dir * 2.2;
if (sc.scrollTop >= Math.min(max, 240)) dir = -1;
else if (sc.scrollTop <= 0) dir = 1;
requestAnimationFrame(step);
}, 1000);
}
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。