ヘッダー 初級

告知バー付きヘッダー

ヘッダー上部にキャンペーン告知バー(カウントダウン+閉じる)を重ねた構成。閉じるとバーがスッと畳まれ本体が上へ詰まります。セール・新機能・期間限定訴求の常套手段です。

#header#announcement#countdown#navigation

ライブデモ

使用例(お題: SaaS FlowDesk)

この技法を「SaaS FlowDesk」というテーマのダミーサイトで実際に使った例です。

HTML
<!-- FlowDesk:SaaSの期間限定セール告知バー -->
<div class="hab-frame">
  <div class="hab-stack" id="habStack">
    <div class="hab-bar" id="habBar">
      <span class="hab-bar__dot"></span>
      <p class="hab-bar__text">新年度キャンペーン — 年間プラン <b>40%OFF</b>、終了まで <time id="habTime">02:59:59</time></p>
      <button class="hab-bar__close" id="habClose" type="button" aria-label="告知を閉じる">✕</button>
    </div>
    <header class="hab-head">
      <a class="hab-logo" href="#" onclick="return false">◇ FlowDesk</a>
      <nav class="hab-nav">
        <a href="#" onclick="return false">機能</a>
        <a href="#" onclick="return false">料金</a>
        <a href="#" onclick="return false">導入事例</a>
      </nav>
      <button class="hab-cta" type="button">プランを見る</button>
    </header>
  </div>

  <div class="hab-stage">
    <h1>段取りを、ひとつに。</h1>
    <p>上部の告知バーで期間限定オファーを訴求。<br>✕で閉じるとヘッダーが上へ詰まります。</p>
  </div>
</div>
CSS
/* FlowDesk(SaaS):告知バー付きヘッダーの再スキン */
* { box-sizing: border-box; }
body { margin: 0; font-family: "Segoe UI", system-ui, -apple-system, sans-serif; }

.hab-frame { position: relative; width: 100%; height: 380px; overflow: hidden; background: #f4f6fc; }
.hab-stack { position: absolute; top: 0; left: 0; right: 0; z-index: 10; }

.hab-bar {
  display: flex; align-items: center; gap: 10px; padding: 9px 18px;
  max-height: 44px; overflow: hidden; color: #fff;
  background: linear-gradient(90deg, #3b5bff, #6d28d9);
  transition: max-height .35s ease, padding .35s ease, opacity .3s ease;
}
.hab-bar.is-closed { max-height: 0; padding-top: 0; padding-bottom: 0; opacity: 0; }
.hab-bar__dot { width: 8px; height: 8px; border-radius: 50%; background: #fff; animation: hab-pulse 1.4s ease-in-out infinite; }
.hab-bar__text { margin: 0 auto 0 0; font-size: 12.5px; }
.hab-bar__text b { font-weight: 800; }
.hab-bar__text time { font-variant-numeric: tabular-nums; font-weight: 700; }
.hab-bar__close { background: none; border: none; color: #fff; cursor: pointer; font-size: 13px; opacity: .85; padding: 4px; }
.hab-bar__close:hover { opacity: 1; }
@keyframes hab-pulse { 0%,100% { transform: scale(1); opacity: 1; } 50% { transform: scale(.55); opacity: .5; } }

.hab-head { display: flex; align-items: center; gap: 18px; height: 56px; padding: 0 20px; background: #fff; border-bottom: 1px solid #e7eaf3; box-shadow: 0 4px 14px rgba(20,28,60,.05); }
.hab-logo { font-size: 18px; font-weight: 800; color: #1f2547; text-decoration: none; }
.hab-nav { display: flex; gap: 4px; margin-left: auto; }
.hab-nav a { color: #3a4060; text-decoration: none; font-size: 13px; font-weight: 600; padding: 7px 11px; border-radius: 8px; transition: background .2s; }
.hab-nav a:hover { background: #eef1fc; color: #3b5bff; }
.hab-cta { font: inherit; font-size: 12.5px; font-weight: 700; color: #fff; cursor: pointer; border: none; padding: 8px 15px; border-radius: 10px; background: #3b5bff; transition: filter .15s, transform .15s; }
.hab-cta:hover { filter: brightness(1.07); transform: translateY(-1px); }

.hab-stage { display: grid; place-content: center; height: 100%; text-align: center; color: #2a3050; padding: 0 24px; }
.hab-stage h1 { margin: 0; font-size: 26px; font-weight: 800; }
.hab-stage p { margin: 12px 0 0; font-size: 13px; line-height: 1.8; color: #6a7090; }

@media (prefers-reduced-motion: reduce) { .hab-bar, .hab-cta { transition: none; } .hab-bar__dot { animation: none; } }
JavaScript
// (デモと同じフックを流用)カウントダウン+閉じる、自動で閉開も実演
(() => {
  const bar = document.getElementById('habBar');
  const close = document.getElementById('habClose');
  const time = document.getElementById('habTime');
  if (!bar || !close || !time) return;
  let remain = 3 * 3600 - 1;
  const pad = (n) => String(n).padStart(2, '0');
  setInterval(() => {
    remain = remain > 0 ? remain - 1 : 3 * 3600 - 1;
    time.textContent = `${pad(Math.floor(remain / 3600))}:${pad(Math.floor((remain % 3600) / 60))}:${pad(remain % 60)}`;
  }, 1000);
  let auto = !matchMedia('(prefers-reduced-motion: reduce)').matches;
  close.addEventListener('click', () => { auto = false; bar.classList.add('is-closed'); });
  if (auto) {
    let closed = false;
    const tick = () => { if (!auto) return; closed = !closed; bar.classList.toggle('is-closed', closed); setTimeout(tick, closed ? 1800 : 3000); };
    setTimeout(tick, 2600);
  }
})();

実装ガイド

使いどころ

セール・新機能・期間限定など、全ページ最上部で強く訴求したいときに。ヘッダーの上に告知バーを積み、カウントダウンで緊急性を演出。閉じるとバーが畳まれ本体が上へ詰まります。

実装時の注意点

バーの開閉は max-height と padding のトランジションで、高さを 0 にして滑らかに畳みます。カウントダウンは setInterval で毎秒デクリメントし、0 で巻き戻してループ。閉じる操作で is-closed を付与します。

対応ブラウザ

max-height トランジション・setInterval・tabular-nums いずれも全モダンブラウザで対応します。特別な対応は不要です。

よくある失敗

max-height で畳む方式は中身の高さを上回る値を指定する必要があり、内容が増えると一瞬カクつくことがあります(その場合は実測値か grid-template-rows:0fr→1fr を使用)。実運用では閉じた状態を localStorage に保存し、再訪時に出さない配慮を。カウントダウンは実際の終了時刻(サーバ時刻)から算出し、リロードで戻らないようにしてください。

応用例

クリックで割引コードをコピー、残数表示と連動、A/Bで文言を出し分け、スクロールでバーだけ先に隠す、などの拡張が定番です。

コード

HTML
<!-- 告知バー+ヘッダー(閉じると本体が上に詰まる) -->
<div class="hab-frame">
  <div class="hab-stack" id="habStack">
    <!-- 上部の告知バー -->
    <div class="hab-bar" id="habBar">
      <span class="hab-bar__dot"></span>
      <p class="hab-bar__text">期間限定セール開催中 — 全プラン <b>30%OFF</b>、残り <time id="habTime">02:59:59</time></p>
      <button class="hab-bar__close" id="habClose" type="button" aria-label="告知を閉じる">✕</button>
    </div>
    <!-- ヘッダー本体 -->
    <header class="hab-head">
      <a class="hab-logo" href="#" onclick="return false">Nimbus</a>
      <nav class="hab-nav">
        <a href="#" onclick="return false">機能</a>
        <a href="#" onclick="return false">料金</a>
        <a href="#" onclick="return false">導入事例</a>
      </nav>
      <button class="hab-cta" type="button">セールを見る</button>
    </header>
  </div>

  <div class="hab-stage">
    <h1>告知バー+ヘッダー</h1>
    <p>カウントダウンで緊急性を演出。<br>✕で閉じるとバーが畳まれ、ヘッダーが上へ詰まります。</p>
  </div>
</div>
CSS
* { box-sizing: border-box; }
body { margin: 0; font-family: "Segoe UI", system-ui, -apple-system, sans-serif; }

.hab-frame { position: relative; width: 100%; height: 380px; overflow: hidden; background: #f5f6fa; }

/* バー+ヘッダーを縦に積む。バーの開閉は max-height で */
.hab-stack { position: absolute; top: 0; left: 0; right: 0; z-index: 10; }

.hab-bar {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 18px;
  max-height: 44px; overflow: hidden;
  color: #fff;
  background: linear-gradient(90deg, #4f46e5, #db2777);
  transition: max-height .35s ease, padding .35s ease, opacity .3s ease;
}
.hab-bar.is-closed { max-height: 0; padding-top: 0; padding-bottom: 0; opacity: 0; }
.hab-bar__dot { width: 8px; height: 8px; border-radius: 50%; background: #fff; animation: hab-pulse 1.4s ease-in-out infinite; }
.hab-bar__text { margin: 0 auto 0 0; font-size: 12.5px; }
.hab-bar__text b { font-weight: 800; }
.hab-bar__text time { font-variant-numeric: tabular-nums; font-weight: 700; }
.hab-bar__close { background: none; border: none; color: #fff; cursor: pointer; font-size: 13px; opacity: .85; padding: 4px; }
.hab-bar__close:hover { opacity: 1; }

@keyframes hab-pulse { 0%,100% { transform: scale(1); opacity: 1; } 50% { transform: scale(.55); opacity: .5; } }

.hab-head {
  display: flex; align-items: center; gap: 18px;
  height: 56px; padding: 0 20px;
  background: #fff; border-bottom: 1px solid #ebedf3;
  box-shadow: 0 4px 14px rgba(20,24,40,.05);
}
.hab-logo { font-size: 18px; font-weight: 800; color: #1f2433; text-decoration: none; }
.hab-nav { display: flex; gap: 4px; margin-left: auto; }
.hab-nav a { color: #3a3f4b; text-decoration: none; font-size: 13px; font-weight: 600; padding: 7px 11px; border-radius: 8px; transition: background .2s; }
.hab-nav a:hover { background: #eef1fb; color: #4f46e5; }
.hab-cta { font: inherit; font-size: 12.5px; font-weight: 700; color: #fff; cursor: pointer; border: none; padding: 8px 15px; border-radius: 999px; background: #db2777; transition: filter .15s, transform .15s; }
.hab-cta:hover { filter: brightness(1.07); transform: translateY(-1px); }

.hab-stage { display: grid; place-content: center; height: 100%; text-align: center; color: #2a2e38; padding: 0 24px; }
.hab-stage h1 { margin: 0; font-size: 26px; font-weight: 800; }
.hab-stage p { margin: 12px 0 0; font-size: 13px; line-height: 1.8; color: #6b7180; }

@media (prefers-reduced-motion: reduce) {
  .hab-bar, .hab-cta { transition: none; }
  .hab-bar__dot { animation: none; }
}
JavaScript
// 告知バー:ライブのカウントダウン+閉じる。プレビューでは自動で閉→開も実演
(() => {
  const bar = document.getElementById('habBar');
  const close = document.getElementById('habClose');
  const time = document.getElementById('habTime');
  if (!bar || !close || !time) return;

  // カウントダウン(0で巻き戻してループ)
  let remain = 3 * 3600 - 1; // 02:59:59
  const pad = (n) => String(n).padStart(2, '0');
  setInterval(() => {
    remain = remain > 0 ? remain - 1 : 3 * 3600 - 1;
    const h = Math.floor(remain / 3600);
    const m = Math.floor((remain % 3600) / 60);
    const s = remain % 60;
    time.textContent = `${pad(h)}:${pad(m)}:${pad(s)}`;
  }, 1000);

  let auto = !matchMedia('(prefers-reduced-motion: reduce)').matches;
  close.addEventListener('click', () => { auto = false; bar.classList.add('is-closed'); });

  // 自動デモ:閉じる→開くを繰り返してヘッダーの詰まりを見せる
  if (auto) {
    let closed = false;
    const tick = () => {
      if (!auto) return;
      closed = !closed;
      bar.classList.toggle('is-closed', closed);
      setTimeout(tick, closed ? 1800 : 3000);
    };
    setTimeout(tick, 2600);
  }
})();

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「告知バー付きヘッダー」の効果を追加してください。

# 追加してほしい効果
告知バー付きヘッダー(ヘッダー)
ヘッダー上部にキャンペーン告知バー(カウントダウン+閉じる)を重ねた構成。閉じるとバーがスッと畳まれ本体が上へ詰まります。セール・新機能・期間限定訴求の常套手段です。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 告知バー+ヘッダー(閉じると本体が上に詰まる) -->
<div class="hab-frame">
  <div class="hab-stack" id="habStack">
    <!-- 上部の告知バー -->
    <div class="hab-bar" id="habBar">
      <span class="hab-bar__dot"></span>
      <p class="hab-bar__text">期間限定セール開催中 — 全プラン <b>30%OFF</b>、残り <time id="habTime">02:59:59</time></p>
      <button class="hab-bar__close" id="habClose" type="button" aria-label="告知を閉じる">✕</button>
    </div>
    <!-- ヘッダー本体 -->
    <header class="hab-head">
      <a class="hab-logo" href="#" onclick="return false">Nimbus</a>
      <nav class="hab-nav">
        <a href="#" onclick="return false">機能</a>
        <a href="#" onclick="return false">料金</a>
        <a href="#" onclick="return false">導入事例</a>
      </nav>
      <button class="hab-cta" type="button">セールを見る</button>
    </header>
  </div>

  <div class="hab-stage">
    <h1>告知バー+ヘッダー</h1>
    <p>カウントダウンで緊急性を演出。<br>✕で閉じるとバーが畳まれ、ヘッダーが上へ詰まります。</p>
  </div>
</div>

【CSS】
* { box-sizing: border-box; }
body { margin: 0; font-family: "Segoe UI", system-ui, -apple-system, sans-serif; }

.hab-frame { position: relative; width: 100%; height: 380px; overflow: hidden; background: #f5f6fa; }

/* バー+ヘッダーを縦に積む。バーの開閉は max-height で */
.hab-stack { position: absolute; top: 0; left: 0; right: 0; z-index: 10; }

.hab-bar {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 18px;
  max-height: 44px; overflow: hidden;
  color: #fff;
  background: linear-gradient(90deg, #4f46e5, #db2777);
  transition: max-height .35s ease, padding .35s ease, opacity .3s ease;
}
.hab-bar.is-closed { max-height: 0; padding-top: 0; padding-bottom: 0; opacity: 0; }
.hab-bar__dot { width: 8px; height: 8px; border-radius: 50%; background: #fff; animation: hab-pulse 1.4s ease-in-out infinite; }
.hab-bar__text { margin: 0 auto 0 0; font-size: 12.5px; }
.hab-bar__text b { font-weight: 800; }
.hab-bar__text time { font-variant-numeric: tabular-nums; font-weight: 700; }
.hab-bar__close { background: none; border: none; color: #fff; cursor: pointer; font-size: 13px; opacity: .85; padding: 4px; }
.hab-bar__close:hover { opacity: 1; }

@keyframes hab-pulse { 0%,100% { transform: scale(1); opacity: 1; } 50% { transform: scale(.55); opacity: .5; } }

.hab-head {
  display: flex; align-items: center; gap: 18px;
  height: 56px; padding: 0 20px;
  background: #fff; border-bottom: 1px solid #ebedf3;
  box-shadow: 0 4px 14px rgba(20,24,40,.05);
}
.hab-logo { font-size: 18px; font-weight: 800; color: #1f2433; text-decoration: none; }
.hab-nav { display: flex; gap: 4px; margin-left: auto; }
.hab-nav a { color: #3a3f4b; text-decoration: none; font-size: 13px; font-weight: 600; padding: 7px 11px; border-radius: 8px; transition: background .2s; }
.hab-nav a:hover { background: #eef1fb; color: #4f46e5; }
.hab-cta { font: inherit; font-size: 12.5px; font-weight: 700; color: #fff; cursor: pointer; border: none; padding: 8px 15px; border-radius: 999px; background: #db2777; transition: filter .15s, transform .15s; }
.hab-cta:hover { filter: brightness(1.07); transform: translateY(-1px); }

.hab-stage { display: grid; place-content: center; height: 100%; text-align: center; color: #2a2e38; padding: 0 24px; }
.hab-stage h1 { margin: 0; font-size: 26px; font-weight: 800; }
.hab-stage p { margin: 12px 0 0; font-size: 13px; line-height: 1.8; color: #6b7180; }

@media (prefers-reduced-motion: reduce) {
  .hab-bar, .hab-cta { transition: none; }
  .hab-bar__dot { animation: none; }
}

【JavaScript】
// 告知バー:ライブのカウントダウン+閉じる。プレビューでは自動で閉→開も実演
(() => {
  const bar = document.getElementById('habBar');
  const close = document.getElementById('habClose');
  const time = document.getElementById('habTime');
  if (!bar || !close || !time) return;

  // カウントダウン(0で巻き戻してループ)
  let remain = 3 * 3600 - 1; // 02:59:59
  const pad = (n) => String(n).padStart(2, '0');
  setInterval(() => {
    remain = remain > 0 ? remain - 1 : 3 * 3600 - 1;
    const h = Math.floor(remain / 3600);
    const m = Math.floor((remain % 3600) / 60);
    const s = remain % 60;
    time.textContent = `${pad(h)}:${pad(m)}:${pad(s)}`;
  }, 1000);

  let auto = !matchMedia('(prefers-reduced-motion: reduce)').matches;
  close.addEventListener('click', () => { auto = false; bar.classList.add('is-closed'); });

  // 自動デモ:閉じる→開くを繰り返してヘッダーの詰まりを見せる
  if (auto) {
    let closed = false;
    const tick = () => {
      if (!auto) return;
      closed = !closed;
      bar.classList.toggle('is-closed', closed);
      setTimeout(tick, closed ? 1800 : 3000);
    };
    setTimeout(tick, 2600);
  }
})();

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。