ナビゲーション
縦セクションナビゲーション
左側ナビに見出し、右側本文を紐づけて、スクロール位置と選択状態を確認するレシピです。
動きを試す
プレビュー
必要なファイルを選ぶ
コード
index.html
利用前に変更する箇所
- 見出し構造を実際の章タイトルへ変更する
<!doctype html>
<html lang="ja" data-theme="light">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>縦セクションナビゲーション</title>
<style>
:root {
--bg: #f7f8fa;
--surface: #ffffff;
--text: #18212f;
--muted: #5d6b7a;
--border: #d7dee8;
--accent: #245fd6;
--focus: #0077cc;
}
:root[data-theme="dark"] {
--bg: #0f141a;
--surface: #171d26;
--text: #f2f5f8;
--muted: #aab4c0;
--border: #354152;
--accent: #7ea7ff;
--focus: #65c7ff;
}
* { box-sizing: border-box; }
body {
margin: 0;
padding: 18px;
background: var(--bg);
color: var(--text);
font-family: Arial, sans-serif;
}
.shell {
display: grid;
grid-template-columns: 220px 1fr;
gap: 12px;
max-width: 760px;
margin: 0 auto;
}
.nav, .content {
padding: 12px;
border: 1px solid var(--border);
background: var(--surface);
border-radius: 10px;
}
.nav a {
display: block;
width: 100%;
min-height: 44px;
margin-bottom: 6px;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--surface);
color: var(--text);
text-decoration: none;
}
.nav a[aria-current="location"] { border-color: var(--accent); font-weight: 700; }
.section { padding: 10px 0; border-bottom: 1px solid var(--border); }
.section:last-of-type { border-bottom: 0; }
.section[data-selected="true"] { border-inline-start: 4px solid var(--accent); padding-inline-start: 10px; }
.status { min-height: 1.2em; margin: 0; }
a:focus-visible,
.section:focus-visible {
outline: 3px solid var(--focus);
}
@media (max-width: 720px) {
.shell {
grid-template-columns: 1fr;
}
}
@media (max-width: 360px) {
body { padding: 10px; }
}
@media (prefers-reduced-motion: reduce) {
* { transition: none !important; }
}
</style>
</head>
<body>
<main class="shell">
<nav class="nav" aria-label="本文セクション">
<a href="#section-home" data-section-link="focus-home">ホーム</a>
<a href="#section-usage" data-section-link="all-open">使い方</a>
<a href="#section-policy" data-section-link="focus-policy">ポリシー</a>
</nav>
<section class="content">
<h1>縦型セクションナビゲーション</h1>
<section class="section" id="section-home" tabindex="-1">
<h2>ホーム</h2>
<p>全体説明と主要な導線を確認します。</p>
</section>
<section class="section" id="section-usage" tabindex="-1">
<h2>使い方</h2>
<p>画面操作の流れを確認します。</p>
</section>
<section class="section" id="section-policy" tabindex="-1">
<h2>ポリシー</h2>
<p>利用時の運用条件を確認します。</p>
</section>
<p class="status" role="status" data-status></p>
</section>
</main>
<script>
(() => {
const params = new URLSearchParams(window.location.search);
const instance = params.get('instance') ?? 'missing-instance';
const status = document.querySelector('[data-status]');
const links = Array.from(document.querySelectorAll('[data-section-link]'));
const sections = Array.from(document.querySelectorAll('.section'));
const validStates = new Set(['all-open', 'focus-home', 'focus-policy']);
const setState = (nextState) => {
if (!validStates.has(nextState)) return;
document.documentElement.dataset.state = nextState;
const activeLink = links.find((link) => link.dataset.sectionLink === nextState);
for (const link of links) {
if (link === activeLink) link.setAttribute('aria-current', 'location');
else link.removeAttribute('aria-current');
}
for (const section of sections) {
section.dataset.selected = String(section.id === activeLink?.hash.slice(1));
}
if (status && activeLink) status.textContent = `${activeLink.textContent?.trim()}を選択しています。`;
};
const setTheme = (theme) => {
if (theme === 'dark' || theme === 'light') document.documentElement.dataset.theme = theme;
};
for (const link of links) {
link.addEventListener('click', () => {
const nextState = link.dataset.sectionLink ?? 'all-open';
const target = document.querySelector(link.hash);
setState(nextState);
queueMicrotask(() => target?.focus({ preventScroll: true }));
});
}
window.addEventListener('message', (event) => {
if (event.source !== window.parent || typeof event.data !== 'object' || event.data === null) return;
if (event.data.source !== 'wpl-host' || event.data.instance !== instance) return;
if (event.data.type === 'wpl-preview-state') setState(event.data.value);
if (event.data.type === 'wpl-preview-theme') setTheme(event.data.value);
});
setTheme(params.get('theme') === 'dark' ? 'dark' : 'light');
setState(validStates.has(params.get('state')) ? (params.get('state') ?? 'all-open') : 'all-open');
window.parent.postMessage({ source: 'wpl-preview', type: 'wpl-preview-ready', instance }, '*');
})();
</script>
</body>
</html>
プレビューで試せる動き
- すべて開示: 全セクションを展開して全体を確認できる状態です。
- ホーム重点: 他の章を表示したまま、ホームの章を現在位置として強調します。
- ポリシー重点: ポリシー章へ焦点を寄せる状態。
使い分けを確認する
このレシピが合う場面
適する場面
- 長文ガイドの章構成を一覧で誘導する
- 運用規定など段落単位確認画面でスクロール誘導する
- 章ごとの要約を表示しながら進捗を示す
適さない場面
- 短いコンテンツしかない1ページ
- 動的に増減する見出しが頻繁に更新される文書
