【Next.js × Tocbot】Tocbotで目次を作成してみよう

はじめに
今回はNext.jsで作成したプロジェクトにTocbotを使用して目次を作成する手順を紹介します。
手軽にいい感じの目次を生成することができます。ぜひお試しください!
Tocbotとは
Tocbotとは、HTML ドキュメントの見出しから目次 (TOC) を作成することができるライブラリです。
当ブログでもTocbotを使用して目次を作成しています。
導入手順
まずは以下のコマンドでTocbotを開発環境にインストールします。
npm install --save tocbot
これでNext.jsのプロジェクトでTocbotを使用する準備が整いました。
※CDNを利用する場合は公式ドキュメントをご参照ください。
目次コンポーネントを作成する
以下のように目次コンポーネントを作成します。
まずはコード全体をご覧ください。
以下はコンポーネントに記述するコードです。
import React, { useEffect } from 'react'; import tocbot from 'tocbot'; const Toc = () => { useEffect(() => { tocbot.init({ tocSelector: '.toc', contentSelector: '.contBody', headingSelector: 'h2, h3', }) return () => { tocbot.destroy() } }) return ( <div> <nav className="toc"/> </div> ) } export default Toc
以下はコンポーネントの呼び出し先の記述です。
import Toc from '../../components/Toc/Toc'; {/* 省略 */} <div className="contBody"> <h2>見出し</h2> <h3>見出し</h3> </div> <Toc/> {/* 省略 */}
コードの詳細(目次コンポーネントを作成する)
それではコードの詳細を解説します。
tocbot.init({ tocSelector: '.toc', contentSelector: '.contBody', headingSelector: 'h2, h3', })
コンポーネント内の上記の記述の解説をします。
- tocSelector・・・目次を作成する要素のクラス名を指定します。
- contentSelector・・・見出し要素(ここではh2・h3)を囲う要素のクラス名を指定します。
- headingSelector・・・対象とする見出し要素を指定します。
return () => { tocbot.destroy() }
上記の記述ではdestroy関数を使用して、不要となったtocbotインスタンスを削除しています。
これで目次が作成できました。
あとは作成した目次にCSSをあてて見た目を作っていきます。
目次にCSSを適用する
CSSを適用する方法としては、公式ドキュメントを参照してCDNを利用するか、独自でCSSを作成して読み込むのどちらかになります。
私の場合は自身で作成したデザインに沿って目次を作成したかったため、後者の方法を採用しました。
今回はコンポーネント内にJSX記法でCSSを記述しました。以下を参考に実装してみてください。
<style jsx>{` .toc-list { } .toc-list-item { } .toc-link { } .is-active-link { } `}</style>
セレクタはほんの一例です。お好みに合わせて調整してみてください。
アクティブなセレクタには「is-active-link」のクラスが付与されるので、アクティブ時のCSSを指定することも可能です。
これでひと通り実装できました!と思いきや、見出し項目にidが付与できていないことに気づきました。
このままでは目次をクリックしてもアンカーリンクが動作しません。
以下で見出し項目にidを付与する手順も紹介します。
見出しにidを付与する
先ほどのコンポーネントの記述に追記していきます。
コード全体は以下のようになります。
import React, { useEffect } from 'react'; import tocbot from 'tocbot'; const Toc = () => { {/* 追記したよ */} const headingAddIds = () => { const targetContainer = document.querySelector('.contBody') if (!targetContainer) { return } const targetHeadings = targetContainer.querySelectorAll('h2, h3'); [].forEach.call(targetHeadings, (targetHeading: HTMLElement) => { const id = targetHeading.textContent if (!targetHeading.getAttribute('id')) { targetHeading.setAttribute('id', id) } }) } {/* 追記したよ */} useEffect(() => { {/* 追記したよ */} headingAddIds() {/* 追記したよ */} tocbot.init({ tocSelector: '.toc', contentSelector: '.contBody', headingSelector: 'h2, h3', }) return () => { tocbot.destroy() } }) return ( <div> <nav className="toc"/> </div> ) } export default Toc
上記のコードの{/* 追記したよ */}の項目をご確認ください。
コードの詳細(見出しにidを付与する)
{/* 追記したよ */} const headingAddIds = () => { const targetContainer = document.querySelector('.contBody') if (!targetContainer) { return } const targetHeadings = targetContainer.querySelectorAll('h2, h3'); [].forEach.call(targetHeadings, (targetHeading: HTMLElement) => { const id = targetHeading.textContent if (!targetHeading.getAttribute('id')) { targetHeading.setAttribute('id', id) } }) } {/* 追記したよ */}
ここでは見出しにidを付与する関数をheadingAddIdsとして用意しました。
headingAddIds関数内では以下の2つの動作をしています。
- 見出し要素(h2・h3)を囲う要素(contBody)の有無の判定
- 見出し要素(h2・h3)のテキスト情報を取得して、取得したテキスト情報をidとして付与
上記で定義した関数をuseEffect内で呼び出します。
{/* 追記したよ */} headingAddIds() {/* 追記したよ */}
以上で見出し要素にidを付与することができました。
まとめ
今回は目次生成ライブラリであるTocbotの使用方法をまとめてみました。
ライブラリを使用せずに一から実装するとなると大変ですが、Tocbotを使用することであまり手をかけずに実装することができました。
ぜひ一度お試しいただけたらと思います!

執筆者:Nobu
1994年生まれ。
Webフロントエンドエンジニアは3年目。
コーヒーと読書が好きです。主にコーヒー豆の紹介や読んだ本の紹介をしていこうと思います。