ブログにTOCを実装


目次(TableOfContent)というやつですね

要件

・各記事のh2項目を目次とする
・各記事の右側に表示する
・スクロールに追従する
・各項目はh2へのリンクになっている

よくあるタイプ

目次項目の抽出

Astroさんが大体用意してくれているので意外とあっさりできました
まずAstroのブログテンプレートで作成した状態の[..slug].astroで

const post = Astro.props;
const { Content, headings } = await post.render();

const toc = headings.filter((heading) => heading.depth === 2)

Contentを取得している所でheadingsも取得できます
今回はh2要素が欲しいのでdepth==2で

TOCを横に並べる

後述で作成したTOCを横に置きます
そのまま置くと下に置かれるのでflexに

<style>
	.blog-post {
		display: flex;
	}
</style>
<BlogPost {...post.data}>
	<div class="blog-post">
		<div>
			<Content />
		</div>
		<div>
			<TOC headings={toc} />
		</div>
	</div>
</BlogPost>

 

TOCコンポーネント

今横に出てるもののコードは下記
これにheadingsとして前項目でフィルタしたものを渡してやるとOKな感じで
40行の簡素なもの

---
import type { MarkdownHeading } from 'astro';


interface Props {
    headings: MarkdownHeading[];
}

const { headings } = Astro.props;

---
<style>
    a {
        text-decoration: none;
        color: #333;
    }
    .toc {
        position: sticky;
        top: 0;
        right: 0;
        padding: 1rem;
        background-color: #f8f8f8;
        border: 1px solid #e1e1e1;
        border-radius: 5px;
    }
    .item {
        font-size: small;
        margin-left: 1rem;
    }
</style>
<ul class="toc">
    <div>
        {headings.map((heading) => (
            <li class="item list-disc">
                <a href={`#${heading.slug}`}><font size="3">{heading.text}</font></a>
            </li>
        ))}
    </div>
</ul>

ヘッダがある関係で少しおかしい挙動するけどまぁヨシ!