ナビゲーション
アンカー型目次ナビ
長文を章単位で跳ねる目次の配置と、長い見出しでも崩れない導線を確認するレシピです。
動きを試す
プレビュー
必要なファイルを選ぶ
コード
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: 16px; background: var(--bg); color: var(--text); font-family: Arial, sans-serif; }
.layout { max-width: 760px; margin: 0 auto; display: grid; grid-template-columns: 220px 1fr; gap: 12px; }
.toc, .content { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 12px; }
ul { margin: 0; padding-left: 16px; }
a { color: var(--accent); }
[data-optional-entry][hidden] { display: none; }
[aria-current="location"] { font-weight: 700; }
h2:focus-visible { outline: 3px solid var(--focus); }
.status { min-height: 1.4em; }
@media (max-width: 720px) {
.layout { grid-template-columns: 1fr; }
}
@media (max-width: 360px) { body { padding: 10px; } }
@media (prefers-reduced-motion: reduce) { * { transition-duration: 0.01ms !important; animation: none !important; } }
</style>
</head>
<body>
<main class="layout">
<nav class="toc" aria-label="目次">
<p>目次(簡易)</p>
<ul>
<li><a href="#ch1" data-jump="compact">導入</a></li>
<li><a href="#ch2" data-jump="expanded">運用条件</a></li>
<li data-optional-entry><a href="#ch3" data-jump="focused">設計ノート</a></li>
</ul>
</nav>
<article class="content">
<h1>アンカー目次レシピ</h1>
<section>
<h2 id="ch1" tabindex="-1">導入</h2>
<p>簡易目次では主要見出しのみを表示します。</p>
</section>
<section>
<h2 id="ch2" tabindex="-1">運用条件</h2>
<ul>
<li>見出し1</li>
<li>見出し2(長い見出しを折り返せるか確認)</li>
<li>見出し3(補足ルール)</li>
</ul>
</section>
<section>
<h2 id="ch3" tabindex="-1">設計ノート</h2>
<p>現在選択中の章をフォーカス表示します。</p>
</section>
<p class="status" data-status role="status"></p>
</article>
</main>
<script>
(() => {
const params = new URLSearchParams(window.location.search);
const instance = params.get('instance') ?? 'missing-instance';
const links = Array.from(document.querySelectorAll('[data-jump]'));
const optionalEntries = Array.from(document.querySelectorAll('[data-optional-entry]'));
const status = document.querySelector('[data-status]');
const validStates = new Set(['compact', 'expanded', 'focused']);
const setCurrentLink = (currentLink) => {
for (const link of links) link.removeAttribute('aria-current');
currentLink?.setAttribute('aria-current', 'location');
};
const setState = (nextState, currentLink = null) => {
if (!validStates.has(nextState)) return;
document.documentElement.dataset.state = nextState;
for (const entry of optionalEntries) entry.hidden = nextState === 'compact';
setCurrentLink(nextState === 'focused' ? (currentLink ?? links.find((link) => link.dataset.jump === 'focused')) : null);
if (status) status.textContent = nextState === 'focused' ? '選択した章へ移動しました。' : '目次の表示を更新しました。';
};
const setTheme = (theme) => {
if (theme === 'dark' || theme === 'light') document.documentElement.dataset.theme = theme;
};
for (const link of links) {
link.addEventListener('click', () => {
const targetId = link.hash.slice(1);
const target = document.getElementById(targetId);
if (!target) return;
setState('focused', link);
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') ?? 'compact') : 'compact');
window.parent.postMessage({ source: 'wpl-preview', type: 'wpl-preview-ready', instance }, '*');
})();
</script>
</body>
</html>
プレビューで試せる動き
- 簡易目次: 主要見出しを2件に絞って表示します。
- 詳細目次: 3件すべての主要見出しを表示します。
- 選択中: 現在表示中の見出しへハイライトを移動します。
使い分けを確認する
このレシピが合う場面
適する場面
- 技術資料・方針文書で目次導線を設計する
- 長い見出しを含む章移動を確認する
- 目次から本文位置へ遷移する導線を明確化する
適さない場面
- ページ内移動が不要な短文ページ
- リンクの有効性が低いCMS自動生成ページ
