Astroサイトに目次をつける
前回の記事から、記事に目次をつけるようにした。自由に書いているときなら必要ないかもしれないが、この記事のようなまじめなものにはついているほうが「それっぽい」と思う。今回は、Astroで構築したブログに折りたたみ可能な目次を実装する方法についてだ。
目次
必要なパッケージ
まずは必要なパッケージをインストールする。以下の三つが必要。
rehypeSlug: 記事内の### これは見出し1ですみたいな部分をHTMLにするときに<h3 id="これは見出し1です">これは見出し1です<h3>のようにidを設定するremark-toc: 後述する設定に基づいた場所に目次を生成するremark-collapse: 生成された目次を折りたためるようにする
設定方法
次にこれらの設定をastro.config.mjsに書く。私のものは以下の通り。
import remarkToc from 'remark-toc';import remarkCollapse from 'remark-collapse';import rehypeSlug from 'rehype-slug';
export default defineConfig({ markdown: { gfm: false, remarkPlugins: [ remarkGfm, remarkAttributes, [remarkToc, { heading: "目次|Contents", maxDepth: 4 }], [remarkCollapse, { test: "目次|Contents", summary: "目次", }] ], rehypePlugins: [ rehypeSlug ], } // ほかの設定は省略});-
rehypeSlug: 特に何も渡さなくても動作する
-
remarkToc:heading: 目次を挿入する場所を示す見出しのテキスト。ここでは「目次」または「Contents」という見出しを作ると、下に目次が生成されるmaxDepth: 目次に含める見出しの最大階層。4を指定するとh4まで目次に含まれるordered: 番号付きリストにするか。デフォルトはfalse
-
remarkCollapse:test: 折りたたみ対象とする見出しのテキストsummary:<details>タグの<summary>要素の内容
スタイルの装飾
表示の問題
さて、これで記事を以下のように書いてみる。
### 目次
### 小見出し1#### 項目1aaa#### 項目2bbb### 小見出し2cccするとおそらくこのように表示される。
「目次」の言葉は二つはいらない。後から生成されるsummaryのほうだけ残して、remark-tocのために入れてある### 目次の部分を見えなくしたい。常に「目次」の言葉を使うなら#目次のCSSを書けばいいが、言葉が変わればidは変わってしまう。目次が一番前になることが確約されているなら、記事の中のhタグから:first-childでdisplay: noneにすればいいが、自分はremark-attributesを入れているのでこのようにも書ける。
### 目次{: .toc-heading}これでこの要素にtoc-headingクラスをつけることができ、このクラスにdisplay: noneをつけている。
CSSの実装例
自分のCSSはこんな感じ
/* 見出しの装飾 */.prose .toc-heading { display: none;}.prose details { user-select: none; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; cursor: pointer;}.prose details a { color: #0066cc;}.prose h3, .prose h4 { position: relative; width: fit-content; margin: .7rem 0;}.prose h3 { font-size: 1.2rem; font-weight: 700; padding: 0 .5rem 0 1.2rem; border-bottom: 2px solid #ff7f7e;}.prose h3::before { content: '#'; position: absolute; top: 0; left: 0; width: 100%; height: 100%; color: #ff7f7e;}.prose h4 { font-size: 1rem; font-weight: 600; padding: 0 .5rem 0 1rem;}.prose h4::before { content: '・'; position: absolute; top: 0; left: 0; width: 100%; height: 100%; color: #ff7f7e;}details要素はユーザーがクリックするところなので、テキストを選択できないようにし、cursor: pointerを設定している。
h3とh4の見出しには、::beforeを使って装飾を追加している(これほんと便利)。h3には#、h4には・を先頭に表示し、h3には下線をつけることで階層の違いが分かりやすくなっていると思う。それぞれのpaddingがややこしいことになっているのは左に#や・のためのスペースを空けるため。