メガメニュー
トップ項目をクリックまたはホバーすると、複数カラムのリンクとおすすめ枠が並ぶ大型パネルが開くメガメニュー。Escや外側クリックでも閉じられます。
ライブデモ
コード
HTML
<!-- クリック/タップ・ホバー・キーボードに対応した本格メガメニュー。2つのトップ項目がそれぞれ複数カラムのパネルを持つ -->
<div class="nmm-frame" id="nmmFrame">
<header class="nmm-head">
<a class="nmm-logo" href="#" onclick="return false">NORDHOLM</a>
<nav class="nmm-nav" id="nmmNav" aria-label="メインナビゲーション">
<div class="nmm-item" data-menu="furniture">
<button class="nmm-trigger" type="button" aria-expanded="false" aria-controls="nmmPanel-furniture">
家具 <span class="nmm-caret" aria-hidden="true">▾</span>
</button>
<div class="nmm-panel" id="nmmPanel-furniture" role="menu">
<div class="nmm-col">
<p class="nmm-col__head">リビング</p>
<a href="#" onclick="return false" role="menuitem">ソファ</a>
<a href="#" onclick="return false" role="menuitem">サイドテーブル</a>
<a href="#" onclick="return false" role="menuitem">テレビボード</a>
</div>
<div class="nmm-col">
<p class="nmm-col__head">ダイニング</p>
<a href="#" onclick="return false" role="menuitem">テーブル</a>
<a href="#" onclick="return false" role="menuitem">チェア</a>
<a href="#" onclick="return false" role="menuitem">ベンチ</a>
</div>
<div class="nmm-col">
<p class="nmm-col__head">寝室</p>
<a href="#" onclick="return false" role="menuitem">ベッドフレーム</a>
<a href="#" onclick="return false" role="menuitem">マットレス</a>
<a href="#" onclick="return false" role="menuitem">ナイトテーブル</a>
</div>
<div class="nmm-col nmm-col--promo">
<p class="nmm-promo__tag">新作</p>
<p class="nmm-promo__title">オーク無垢材コレクション</p>
<p class="nmm-promo__text">経年変化を愉しむ無垢材の新シリーズ。</p>
<span class="nmm-promo__btn">見る →</span>
</div>
</div>
</div>
<div class="nmm-item" data-menu="storage">
<button class="nmm-trigger" type="button" aria-expanded="false" aria-controls="nmmPanel-storage">
収納 <span class="nmm-caret" aria-hidden="true">▾</span>
</button>
<div class="nmm-panel" id="nmmPanel-storage" role="menu">
<div class="nmm-col">
<p class="nmm-col__head">シェルフ</p>
<a href="#" onclick="return false" role="menuitem">オープンラック</a>
<a href="#" onclick="return false" role="menuitem">本棚</a>
</div>
<div class="nmm-col">
<p class="nmm-col__head">クローゼット</p>
<a href="#" onclick="return false" role="menuitem">ワードローブ</a>
<a href="#" onclick="return false" role="menuitem">チェスト</a>
</div>
<div class="nmm-col nmm-col--promo">
<p class="nmm-promo__tag">セール</p>
<p class="nmm-promo__title">収納家具 最大30%OFF</p>
<p class="nmm-promo__text">今だけの数量限定セール。</p>
<span class="nmm-promo__btn">詳しく →</span>
</div>
</div>
</div>
<a class="nmm-link" href="#" onclick="return false">新着</a>
<a class="nmm-link" href="#" onclick="return false">セール</a>
<a class="nmm-link" href="#" onclick="return false">店舗情報</a>
</nav>
</header>
<div class="nmm-stage">
<p class="nmm-hint">「家具」「収納」はクリックまたはホバーでメガパネルが開きます。<br><small>(Escキーや外側クリックで閉じます。自動でも実演します)</small></p>
</div>
</div>
CSS
* { box-sizing: border-box; }
body { margin: 0; display: block; background: #12181f; font-family: "Hiragino Sans", "Segoe UI", system-ui, sans-serif; }
/* ルート: width:100% + height:100vh でカード/モーダルどちらの枠にも隙間なく収まる */
.nmm-frame { position: relative; width: 100%; height: 100vh; overflow: hidden; background: #12181f; }
.nmm-head {
position: relative; z-index: 20;
display: flex; align-items: center; gap: clamp(14px, 4vw, 30px);
height: 64px; padding: 0 clamp(14px, 3vw, 28px);
background: #12181f; border-bottom: 1px solid #232b36;
}
.nmm-logo { font-size: 16px; font-weight: 800; letter-spacing: .16em; color: #f4ede3; text-decoration: none; flex-shrink: 0; }
.nmm-nav {
display: flex; align-items: center; gap: 4px;
overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none;
}
.nmm-nav::-webkit-scrollbar { display: none; }
.nmm-item { position: static; flex-shrink: 0; }
.nmm-trigger, .nmm-link {
font: inherit; font-size: 13px; font-weight: 600; color: #c7cdd6;
background: none; border: none; cursor: pointer; text-decoration: none; white-space: nowrap;
padding: 8px 12px; border-radius: 8px;
display: inline-flex; align-items: center; gap: 5px;
transition: background .2s ease, color .2s ease;
}
.nmm-trigger:hover, .nmm-link:hover { background: #1d2530; color: #e0a458; }
.nmm-caret { font-size: 10px; transition: transform .25s ease; }
.nmm-item.is-open .nmm-trigger { background: #1d2530; color: #e0a458; }
.nmm-item.is-open .nmm-caret { transform: rotate(180deg); }
.nmm-panel {
position: absolute; top: 64px; left: 16px; right: 16px; z-index: 25;
display: grid; grid-template-columns: repeat(3, 1fr) 1.1fr; gap: 8px;
padding: 20px; border-radius: 12px;
background: #1a212b; border: 1px solid #2a3340;
box-shadow: 0 30px 60px rgba(0, 0, 0, .45);
opacity: 0; transform: translateY(-8px); pointer-events: none;
transition: opacity .22s ease, transform .22s ease;
}
.nmm-item.is-open .nmm-panel { opacity: 1; transform: translateY(0); pointer-events: auto; }
#nmmPanel-storage { grid-template-columns: repeat(2, 1fr) 1.1fr; }
.nmm-col__head { margin: 0 0 8px; font-size: 10.5px; font-weight: 700; letter-spacing: .12em; color: #6b7686; text-transform: uppercase; }
.nmm-col a {
display: block; font-size: 13px; color: #d7dbe2; text-decoration: none;
padding: 7px 9px; border-radius: 8px; transition: background .18s ease, color .18s ease;
}
.nmm-col a:hover { background: #232c38; color: #e0a458; }
.nmm-col--promo {
background: linear-gradient(150deg, #3a2a1a, #1a212b);
border-radius: 10px; padding: 16px;
display: flex; flex-direction: column; justify-content: center;
}
.nmm-promo__tag { align-self: flex-start; font-size: 9.5px; font-weight: 700; letter-spacing: .12em; color: #12181f; background: #e0a458; padding: 3px 8px; border-radius: 999px; margin: 0 0 8px; }
.nmm-promo__title { margin: 0; font-size: 14.5px; font-weight: 800; color: #f4ede3; line-height: 1.4; }
.nmm-promo__text { margin: 6px 0 10px; font-size: 11.5px; color: #a9b0bc; line-height: 1.6; }
.nmm-promo__btn { font-size: 12px; font-weight: 700; color: #e0a458; }
.nmm-stage { display: grid; place-content: center; height: calc(100% - 64px); text-align: center; padding: 24px; }
.nmm-hint { color: #8b93a1; font-size: 13.5px; line-height: 1.8; max-width: 380px; margin: 0; }
.nmm-hint small { color: #5d6472; }
/* 狭幅:パネルを2カラムに畳んで崩れを防ぐ */
@media (max-width: 720px) {
.nmm-panel, #nmmPanel-storage { grid-template-columns: repeat(2, 1fr); }
.nmm-col--promo { grid-column: span 2; }
}
@media (prefers-reduced-motion: reduce) {
.nmm-panel, .nmm-caret { transition: none; }
}
JavaScript
// クリック/タップ・ホバー(インテント遅延なしの簡易版)・Esc・外側クリックに対応したメガメニュー制御
// プレビューでは自動でも開閉を実演し、実操作が来たら止まる(null安全)
(() => {
const nav = document.getElementById('nmmNav');
if (!nav) return;
const items = [...nav.querySelectorAll('.nmm-item')];
if (!items.length) return;
let auto = !matchMedia('(prefers-reduced-motion: reduce)').matches;
let closeTimer = null;
// クリックされたパネルは「ピン留め」して、ホバー離脱では閉じないようにする。
// これが無いと「ホバーで開く→そのままクリック」という自然な操作で
// クリック側が「開いているのでトグルで閉じる」と誤判定し、即座に閉じてしまう。
const closeAll = (except) => {
items.forEach((item) => {
if (item === except) return;
item.classList.remove('is-open', 'is-pinned');
const t = item.querySelector('.nmm-trigger');
if (t) t.setAttribute('aria-expanded', 'false');
});
};
const open = (item) => {
closeAll(item);
item.classList.add('is-open');
const t = item.querySelector('.nmm-trigger');
if (t) t.setAttribute('aria-expanded', 'true');
};
const close = (item) => {
item.classList.remove('is-open', 'is-pinned');
const t = item.querySelector('.nmm-trigger');
if (t) t.setAttribute('aria-expanded', 'false');
};
items.forEach((item) => {
const trigger = item.querySelector('.nmm-trigger');
if (!trigger) return;
trigger.addEventListener('click', () => {
auto = false;
if (item.classList.contains('is-pinned')) {
close(item);
} else {
open(item);
item.classList.add('is-pinned');
}
});
item.addEventListener('pointerenter', () => {
auto = false;
clearTimeout(closeTimer);
open(item);
});
item.addEventListener('pointerleave', () => {
if (item.classList.contains('is-pinned')) return;
clearTimeout(closeTimer);
closeTimer = setTimeout(() => close(item), 220);
});
item.addEventListener('focusin', () => { auto = false; open(item); });
});
document.addEventListener('click', (e) => {
if (!nav.contains(e.target)) { auto = false; closeAll(); }
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') { auto = false; closeAll(); }
});
if (auto) {
let idx = 0;
const tick = () => {
if (!auto) return;
closeAll();
open(items[idx % items.length]);
idx += 1;
setTimeout(() => {
if (!auto) return;
closeAll();
setTimeout(tick, 900);
}, 2200);
};
setTimeout(tick, 1000);
}
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「メガメニュー」の効果を追加してください。
# 追加してほしい効果
メガメニュー(ヘッダー & ナビ)
トップ項目をクリックまたはホバーすると、複数カラムのリンクとおすすめ枠が並ぶ大型パネルが開くメガメニュー。Escや外側クリックでも閉じられます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- クリック/タップ・ホバー・キーボードに対応した本格メガメニュー。2つのトップ項目がそれぞれ複数カラムのパネルを持つ -->
<div class="nmm-frame" id="nmmFrame">
<header class="nmm-head">
<a class="nmm-logo" href="#" onclick="return false">NORDHOLM</a>
<nav class="nmm-nav" id="nmmNav" aria-label="メインナビゲーション">
<div class="nmm-item" data-menu="furniture">
<button class="nmm-trigger" type="button" aria-expanded="false" aria-controls="nmmPanel-furniture">
家具 <span class="nmm-caret" aria-hidden="true">▾</span>
</button>
<div class="nmm-panel" id="nmmPanel-furniture" role="menu">
<div class="nmm-col">
<p class="nmm-col__head">リビング</p>
<a href="#" onclick="return false" role="menuitem">ソファ</a>
<a href="#" onclick="return false" role="menuitem">サイドテーブル</a>
<a href="#" onclick="return false" role="menuitem">テレビボード</a>
</div>
<div class="nmm-col">
<p class="nmm-col__head">ダイニング</p>
<a href="#" onclick="return false" role="menuitem">テーブル</a>
<a href="#" onclick="return false" role="menuitem">チェア</a>
<a href="#" onclick="return false" role="menuitem">ベンチ</a>
</div>
<div class="nmm-col">
<p class="nmm-col__head">寝室</p>
<a href="#" onclick="return false" role="menuitem">ベッドフレーム</a>
<a href="#" onclick="return false" role="menuitem">マットレス</a>
<a href="#" onclick="return false" role="menuitem">ナイトテーブル</a>
</div>
<div class="nmm-col nmm-col--promo">
<p class="nmm-promo__tag">新作</p>
<p class="nmm-promo__title">オーク無垢材コレクション</p>
<p class="nmm-promo__text">経年変化を愉しむ無垢材の新シリーズ。</p>
<span class="nmm-promo__btn">見る →</span>
</div>
</div>
</div>
<div class="nmm-item" data-menu="storage">
<button class="nmm-trigger" type="button" aria-expanded="false" aria-controls="nmmPanel-storage">
収納 <span class="nmm-caret" aria-hidden="true">▾</span>
</button>
<div class="nmm-panel" id="nmmPanel-storage" role="menu">
<div class="nmm-col">
<p class="nmm-col__head">シェルフ</p>
<a href="#" onclick="return false" role="menuitem">オープンラック</a>
<a href="#" onclick="return false" role="menuitem">本棚</a>
</div>
<div class="nmm-col">
<p class="nmm-col__head">クローゼット</p>
<a href="#" onclick="return false" role="menuitem">ワードローブ</a>
<a href="#" onclick="return false" role="menuitem">チェスト</a>
</div>
<div class="nmm-col nmm-col--promo">
<p class="nmm-promo__tag">セール</p>
<p class="nmm-promo__title">収納家具 最大30%OFF</p>
<p class="nmm-promo__text">今だけの数量限定セール。</p>
<span class="nmm-promo__btn">詳しく →</span>
</div>
</div>
</div>
<a class="nmm-link" href="#" onclick="return false">新着</a>
<a class="nmm-link" href="#" onclick="return false">セール</a>
<a class="nmm-link" href="#" onclick="return false">店舗情報</a>
</nav>
</header>
<div class="nmm-stage">
<p class="nmm-hint">「家具」「収納」はクリックまたはホバーでメガパネルが開きます。<br><small>(Escキーや外側クリックで閉じます。自動でも実演します)</small></p>
</div>
</div>
【CSS】
* { box-sizing: border-box; }
body { margin: 0; display: block; background: #12181f; font-family: "Hiragino Sans", "Segoe UI", system-ui, sans-serif; }
/* ルート: width:100% + height:100vh でカード/モーダルどちらの枠にも隙間なく収まる */
.nmm-frame { position: relative; width: 100%; height: 100vh; overflow: hidden; background: #12181f; }
.nmm-head {
position: relative; z-index: 20;
display: flex; align-items: center; gap: clamp(14px, 4vw, 30px);
height: 64px; padding: 0 clamp(14px, 3vw, 28px);
background: #12181f; border-bottom: 1px solid #232b36;
}
.nmm-logo { font-size: 16px; font-weight: 800; letter-spacing: .16em; color: #f4ede3; text-decoration: none; flex-shrink: 0; }
.nmm-nav {
display: flex; align-items: center; gap: 4px;
overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none;
}
.nmm-nav::-webkit-scrollbar { display: none; }
.nmm-item { position: static; flex-shrink: 0; }
.nmm-trigger, .nmm-link {
font: inherit; font-size: 13px; font-weight: 600; color: #c7cdd6;
background: none; border: none; cursor: pointer; text-decoration: none; white-space: nowrap;
padding: 8px 12px; border-radius: 8px;
display: inline-flex; align-items: center; gap: 5px;
transition: background .2s ease, color .2s ease;
}
.nmm-trigger:hover, .nmm-link:hover { background: #1d2530; color: #e0a458; }
.nmm-caret { font-size: 10px; transition: transform .25s ease; }
.nmm-item.is-open .nmm-trigger { background: #1d2530; color: #e0a458; }
.nmm-item.is-open .nmm-caret { transform: rotate(180deg); }
.nmm-panel {
position: absolute; top: 64px; left: 16px; right: 16px; z-index: 25;
display: grid; grid-template-columns: repeat(3, 1fr) 1.1fr; gap: 8px;
padding: 20px; border-radius: 12px;
background: #1a212b; border: 1px solid #2a3340;
box-shadow: 0 30px 60px rgba(0, 0, 0, .45);
opacity: 0; transform: translateY(-8px); pointer-events: none;
transition: opacity .22s ease, transform .22s ease;
}
.nmm-item.is-open .nmm-panel { opacity: 1; transform: translateY(0); pointer-events: auto; }
#nmmPanel-storage { grid-template-columns: repeat(2, 1fr) 1.1fr; }
.nmm-col__head { margin: 0 0 8px; font-size: 10.5px; font-weight: 700; letter-spacing: .12em; color: #6b7686; text-transform: uppercase; }
.nmm-col a {
display: block; font-size: 13px; color: #d7dbe2; text-decoration: none;
padding: 7px 9px; border-radius: 8px; transition: background .18s ease, color .18s ease;
}
.nmm-col a:hover { background: #232c38; color: #e0a458; }
.nmm-col--promo {
background: linear-gradient(150deg, #3a2a1a, #1a212b);
border-radius: 10px; padding: 16px;
display: flex; flex-direction: column; justify-content: center;
}
.nmm-promo__tag { align-self: flex-start; font-size: 9.5px; font-weight: 700; letter-spacing: .12em; color: #12181f; background: #e0a458; padding: 3px 8px; border-radius: 999px; margin: 0 0 8px; }
.nmm-promo__title { margin: 0; font-size: 14.5px; font-weight: 800; color: #f4ede3; line-height: 1.4; }
.nmm-promo__text { margin: 6px 0 10px; font-size: 11.5px; color: #a9b0bc; line-height: 1.6; }
.nmm-promo__btn { font-size: 12px; font-weight: 700; color: #e0a458; }
.nmm-stage { display: grid; place-content: center; height: calc(100% - 64px); text-align: center; padding: 24px; }
.nmm-hint { color: #8b93a1; font-size: 13.5px; line-height: 1.8; max-width: 380px; margin: 0; }
.nmm-hint small { color: #5d6472; }
/* 狭幅:パネルを2カラムに畳んで崩れを防ぐ */
@media (max-width: 720px) {
.nmm-panel, #nmmPanel-storage { grid-template-columns: repeat(2, 1fr); }
.nmm-col--promo { grid-column: span 2; }
}
@media (prefers-reduced-motion: reduce) {
.nmm-panel, .nmm-caret { transition: none; }
}
【JavaScript】
// クリック/タップ・ホバー(インテント遅延なしの簡易版)・Esc・外側クリックに対応したメガメニュー制御
// プレビューでは自動でも開閉を実演し、実操作が来たら止まる(null安全)
(() => {
const nav = document.getElementById('nmmNav');
if (!nav) return;
const items = [...nav.querySelectorAll('.nmm-item')];
if (!items.length) return;
let auto = !matchMedia('(prefers-reduced-motion: reduce)').matches;
let closeTimer = null;
// クリックされたパネルは「ピン留め」して、ホバー離脱では閉じないようにする。
// これが無いと「ホバーで開く→そのままクリック」という自然な操作で
// クリック側が「開いているのでトグルで閉じる」と誤判定し、即座に閉じてしまう。
const closeAll = (except) => {
items.forEach((item) => {
if (item === except) return;
item.classList.remove('is-open', 'is-pinned');
const t = item.querySelector('.nmm-trigger');
if (t) t.setAttribute('aria-expanded', 'false');
});
};
const open = (item) => {
closeAll(item);
item.classList.add('is-open');
const t = item.querySelector('.nmm-trigger');
if (t) t.setAttribute('aria-expanded', 'true');
};
const close = (item) => {
item.classList.remove('is-open', 'is-pinned');
const t = item.querySelector('.nmm-trigger');
if (t) t.setAttribute('aria-expanded', 'false');
};
items.forEach((item) => {
const trigger = item.querySelector('.nmm-trigger');
if (!trigger) return;
trigger.addEventListener('click', () => {
auto = false;
if (item.classList.contains('is-pinned')) {
close(item);
} else {
open(item);
item.classList.add('is-pinned');
}
});
item.addEventListener('pointerenter', () => {
auto = false;
clearTimeout(closeTimer);
open(item);
});
item.addEventListener('pointerleave', () => {
if (item.classList.contains('is-pinned')) return;
clearTimeout(closeTimer);
closeTimer = setTimeout(() => close(item), 220);
});
item.addEventListener('focusin', () => { auto = false; open(item); });
});
document.addEventListener('click', (e) => {
if (!nav.contains(e.target)) { auto = false; closeAll(); }
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') { auto = false; closeAll(); }
});
if (auto) {
let idx = 0;
const tick = () => {
if (!auto) return;
closeAll();
open(items[idx % items.length]);
idx += 1;
setTimeout(() => {
if (!auto) return;
closeAll();
setTimeout(tick, 900);
}, 2200);
};
setTimeout(tick, 1000);
}
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。