開発blog-platform完了
目次(Table of Contents)機能
概要
ドキュメントページの右側に固定表示される目次(TOC)機能です。現在のスクロール位置に応じて、該当する見出しがハイライト表示されます。
機能仕様
表示対象
- 見出しレベル: H1、H2、H3の見出しを自動抽出
- 表示位置: ページ右側にスティッキー配置
- レスポンシブ: 1024px以下の画面では非表示
ハイライト動作
アクティブ状態(現在位置)
- テキストカラー:
#24292e(通常の黒色) - フォントウェイト:
600(太字) - 左ボーダー: 青色(
#0969da)の2pxボーダー - 背景: 薄い青色のハイライト(
rgba(9, 105, 218, 0.05)) - 透明度:
opacity: 1(完全に表示)
非アクティブ状態
- テキストカラー:
#6e7781(グレー) - 透明度:
opacity: 0.7(薄く表示) - 左ボーダー: なし(
transparent)
ホバー時
- テキストカラー:
#0969da(青色) - 透明度:
opacity: 1(完全に表示)
インデント階層
見出しレベルに応じて左インデントを設定:
- H1:
padding-left: 0.75rem(太字表示) - H2:
padding-left: 1.5rem(標準サイズ) - H3:
padding-left: 2.25rem(小さめのフォント0.8125rem)
技術実装
使用技術
- Vue 3 Composition API
ref,computed,onMounted,onUnmountedを使用- リアクティブな状態管理
- Intersection Observer API
- スクロール位置の検出
- パフォーマンスの最適化
- Nuxt Content v3
doc.body.toc.linksからTOCデータを取得
コンポーネント構成
TableOfContents.vue
Props:
interface TocLink {
id: string // 見出しのID(アンカーリンク用)
text: string // 見出しのテキスト
depth: number // 見出しのレベル(1〜3)
children?: TocLink[] // ネストされた見出し
}
const props = defineProps<{
links?: TocLink[]
}>()
主な機能:
flatLinks: ネストされたリンクをフラット化(H1〜H3のみ)activeId: 現在アクティブな見出しのIDscrollToHeading(): スムーズスクロール機能setupObserver(): Intersection Observerのセットアップ
DocPage.vue
2カラムレイアウトに変更:
<template>
<div class="doc-container">
<article class="doc">
<!-- メインコンテンツ -->
</article>
<aside class="doc-sidebar">
<TableOfContents :links="doc.body?.toc?.links" />
</aside>
</div>
</template>
レイアウト:
.doc-container {
display: grid;
grid-template-columns: 1fr 300px; /* メイン + サイドバー */
gap: 3rem;
}
@media (max-width: 1024px) {
.doc-container {
grid-template-columns: 1fr; /* 1カラムに変更 */
}
.doc-sidebar {
display: none; /* サイドバーを非表示 */
}
}
Intersection Observerの設定
const observer = new IntersectionObserver(
(entries) => {
const visibleEntries = entries.filter(entry => entry.isIntersecting)
if (visibleEntries.length > 0) {
// 最初に表示されている見出しをアクティブにする
const firstVisible = visibleEntries.reduce((prev, curr) => {
return prev.boundingClientRect.top < curr.boundingClientRect.top
? prev
: curr
})
activeId.value = firstVisible.target.id
}
},
{
rootMargin: '-80px 0px -80% 0px', // 上部80px、下部80%をマージン
threshold: 0
}
)
rootMarginの説明:
- 上部 -80px: ヘッダー分を考慮
- 下部 -80%: 画面下部の見出しは無視(スクロールの過敏反応を防ぐ)
スタイリング
目次コンテナ
.toc {
position: sticky;
top: 2rem;
max-height: calc(100vh - 4rem);
overflow-y: auto;
padding: 1.5rem;
background: #f6f8fa;
border-radius: 8px;
border: 1px solid #d0d7de;
}
スクロールバー
.toc::-webkit-scrollbar {
width: 6px;
}
.toc::-webkit-scrollbar-thumb {
background: #d0d7de;
border-radius: 3px;
}
.toc::-webkit-scrollbar-thumb:hover {
background: #afb8c1;
}
使用方法
基本的な使い方
目次は自動的に表示されます。マークダウンファイルにH1〜H3の見出しを記述するだけです。
---
title: "ドキュメントタイトル"
---
# メインタイトル
## セクション1
### サブセクション1.1
## セクション2
### サブセクション2.1
URLハッシュ対応
ページ読み込み時にURLハッシュがある場合、該当の見出しに自動スクロールします。
https://example.com/docs/guide#section-1
↓
#section-1 の見出しにスクロール
パフォーマンス最適化
- Intersection Observer使用
- スクロールイベントよりも効率的
- ブラウザの最適化を活用
- 遅延初期化
- DOMが完全に構築されてから100ms後にオブザーバーをセットアップ
- 初期レンダリングのパフォーマンス向上
- クリーンアップ
- コンポーネントのアンマウント時にオブザーバーを切断
- メモリリークを防止
トラブルシューティング
目次が表示されない
原因1: 見出しがH1〜H3以外
- H4以降の見出しは目次に含まれません
原因2: doc.body.toc.linksがundefined
- Nuxt Contentの設定を確認してください
原因3: 画面幅が1024px以下
- レスポンシブデザインで非表示になります
ハイライトが正しく動作しない
原因: 見出しにIDが設定されていない
- Nuxt Contentが自動的にIDを生成しますが、カスタムIDを設定する場合は注意が必要です
スムーズスクロールが効かない
原因: ブラウザがスムーズスクロールをサポートしていない
- 古いブラウザでは即座にスクロールします
scroll-behavior: smoothのポリフィルを検討してください
今後の拡張案
- モバイル用ドロワーUI
- ハンバーガーメニューから目次を表示
- 進捗インジケーター
- 読み進めた割合を表示
- 折りたたみ機能
- H2, H3を折りたたみ可能に
- 検索機能
- 目次内の見出しを検索
- カスタマイズオプション
- 表示する見出しレベルを設定可能に