告知バー付きヘッダー
ヘッダー上部にキャンペーン告知バー(カウントダウン+閉じる)を重ねた構成。閉じるとバーがスッと畳まれ本体が上へ詰まります。セール・新機能・期間限定訴求の常套手段です。
ライブデモ
使用例(お題: 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で提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。