JavaScript

目次ライブラリの「Tocbot」が超絶便利だった

Tocbotを使って目次を作る手順をまとめました。この記事ではReact/Next.jsとTypeScriptで使っていきます。

目次ライブラリの「Tocbot」が超絶便利だった

コンニチハ!ハルミです。

この記事では、目次を作るライブラリの「Tocbot」を紹介します。

こちらのライブラリは当ブログでも使用しています。
モバイル幅とPC幅で、Tocbotを切り替えているのでその点も参考になるかと思います!

この記事では、React/Next.jsTypeScriptで Tocbot を使って目次を作成していますが、
汎用的な JavaScript のライブラリなので、どの環境でも使えます。

Tocbot とは

Tocbot とは、HTML の見出しタグ(h1, h2, h3 など)から自動的に目次(Table of Contents)を生成する JavaScript ライブラリです。

このライブラリは、Vanilla JavaScriptを使用します。CSS は 350 バイト未満、JavaScript は約 3.6Kb (縮小および gzip 圧縮) と軽量で、依存関係もありません。

読み込み中...

このライブラリを使用することで、指定した要素内の見出しを抽出し、それらをリンク付きの目次として表示することができます。

目次はクリック可能で、該当する見出しにスムーズにスクロールする機能も備えています。

有名なテック記事サイトである「Zenn」でも使用されているみたいです。

読み込み中...

導入

さっそくTocbotを導入していきましょう。

まずは、ターミナルで以下のコマンドを実行して、Tocbotをインストールします。

npm install --save tocbot

もしくは、CDNで読み込むこともできます。

<script src="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.27.4/tocbot.min.js" defer></script>

Next.jsでは、next/headを使用して、以下のように読み込むこともできます。

<Head>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.27.4/tocbot.css" />
</Head>

基本的なスタイルも用意されているみたいなので、スタイリングしない方はこちらも読み込みましょう。この記事ではこちらは使用しません。

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocbot/4.27.4/tocbot.css">

使い方

それでは、実際にTocbotを使って目次を作っていきましょう。
この記事ではReact/Next.jsTypeScriptで使っていきますが、Vanilla JavaScriptでも使えるので、Vanillaで使いたい方は公式ドキュメントを参照してください。

Tocbotコンポーネントを作成

まずは、Tocbotコンポーネントを作成します。
以下はよく使うオプションを指定した雛形です。

Tocbot.tsx
"use client"
import React, { useEffect } from 'react';
import * as tocbot from "tocbot";

export const Tocbot: React.FC = () => {
  useEffect(() => {
    tocbot.init({
      tocSelector: '.table-of-contents',//目次を生成する要素
      contentSelector: '#article-content',//目次にする見出しの親要素
      headingSelector: 'h2, h3, h4',//目次にする見出し
      activeLinkClass: "is-active",//アクティブなリンクのクラス
      listClass: "toc-list",//リストのクラス
      linkClass: "toc-link",//リンクのクラス
      scrollSmoothOffset: -100,//スクロールのオフセット(px)
    });
    return () => {
      tocbot.destroy();//アンマウント時にTocbotを破棄
    };
  }, []);

  return (
    <nav className="table-of-contents" aria-label="目次"/>
  );
};

あとは、Tocbotコンポーネントを設置したい場所に配置するだけです。

page.tsx
import { Tocbot } from "@/components/blog/Tocbot";
import { MDX } from "@/components/blog/MDX";

export default function ArticlePage() {
  return (
    <article id="article-content">
      <MDX />
      <Tocbot />
    </article>
  );
}

オプションの解説

useEffectの中で、Tocbotを初期化しています。
このとき、tocSelectorで目次を生成するラッパー要素を指定し、contentSelectorで目次にする見出しの親要素を指定します。

headingSelectorで目次にする見出しを指定します。

activeLinkClassでアクティブなリンクのクラスを指定します。
ページ内に表示されている目次の見出しに自動的に、ここで指定したクラス名が付与されます。

listClassでリストのクラスを指定します。CSSでスタイリングするのに使えます。

linkClassでリンクのクラスを指定します。CSSでスタイリングするのに使えます。

scrollSmoothOffsetでスクロールのオフセットを指定します。
ヘッダーの分だけスクロールを上にずらしたい要件など多いと思いますが、このオフセットを指定しておくと、目次がヘッダーの下にくるようになります。

その他のオプション

以下は、Tocbotで指定できるオプションの一覧です。

// 目次を表示する場所
tocSelector: '.js-toc',
// または、DOMノードを渡すこともできます
tocElement: null,
// 目次を作成するために見出しを取得する場所
contentSelector: '.js-toc-content',
// または、DOMノードを渡すこともできます
contentElement: null,
// contentSelector要素内で取得する見出し
headingSelector: 'h1, h2, h3',
// ignoreSelectorに一致する見出しはスキップされます
ignoreSelector: '.js-toc-ignore',
// コンテンツ内の相対または絶対位置にある見出し
hasInnerContainers: false,
// リンクに追加するメインクラス
linkClass: 'toc-link',
// リンクに追加する追加クラス
extraLinkClasses: '',
// アクティブなリンクに追加するクラス
activeLinkClass: 'is-active-link',
// リストに追加するメインクラス
listClass: 'toc-list',
// リストに追加する追加クラス
extraListClasses: '',
// リストが折りたたまれるべきときに追加されるクラス
isCollapsedClass: 'is-collapsed',
// リストが折りたたむことができるが、必ずしも折りたたまれていない場合に追加されるクラス
collapsibleClass: 'is-collapsible',
// リストアイテムに追加するクラス
listItemClass: 'toc-list-item',
// アクティブなリストアイテムに追加するクラス
activeListItemClass: 'is-active-li',
// 折りたたまれない見出しレベルの数
collapseDepth: 0,
// スムーズスクロールを有効にする
scrollSmooth: true,
// スムーズスクロールの時間
scrollSmoothDuration: 420,
// スムーズスクロールのオフセット
scrollSmoothOffset: 0,
// スクロール終了時のコールバック
scrollEndCallback: function (e) {},
// 見出しとドキュメントの上部の間のオフセット
headingsOffset: 1,
// イベントが発生する間隔を確保するためのタイムアウト
throttleTimeout: 50,
// positionFixedClassを追加する要素
positionFixedSelector: null,
// サイドバーが固定されるために追加される固定位置クラス
positionFixedClass: 'is-position-fixed',
// fixedSidebarOffsetは任意の数値に設定できますが、デフォルトではautoに設定されています
fixedSidebarOffset: 'auto',
// 見出しノードからHTMLマークアップを含めるかどうか
includeHtml: false,
// リンクのhtmlタイトルタグを見出しに合わせて自動的に設定
includeTitleTags: false,
// 目次内のすべてのリンクに適用するonclick関数
onClick: function (e) {},
// orderedListをfalseに設定すると、順不同リスト(ul)を生成
orderedList: true,
// 固定された記事スクロールコンテナがある場合、オフセットを計算するために設定
scrollContainer: null,
// 外部システムによってすでにレンダリングされている場合、ToCのDOMレンダリングを防ぐ
skipRendering: false,
// 見出しラベルを変更するためのオプションのコールバック
headingLabelCallback: false,
// DOM内で非表示の見出しを無視する
ignoreHiddenElements: false,
// 解析された見出しのプロパティを変更するためのオプションのコールバック
headingObjectCallback: null,
// 基本パスを設定、head内で`base`タグを使用する場合に便利
basePath: '',
// tocSelectorがスクロールしているときにのみ影響し、目次のスクロール位置をコンテンツと同期させる
disableTocScrollSync: false,
// ページをスクロールするときの目次のスクロール(上)位置のオフセット
tocScrollOffset: 0,
// ユーザーがページをスクロールする際に、適切な見出しIDでURLハッシュを更新する
enableUrlHashUpdateOnScroll: false

特殊な状況下での注意点

これは私が経験した状況なのですが、
ページ内にTocbotを複数配置して画面幅に応じて切り替えるような実装をしたい場合、tocbot.init()は正常に動作するのですが、
useEffectのアンマウント時にtocbot.destroy()が発火すると全てのTocbotが破棄されてしまうようです。

そのため、それぞれのTocbotに対して、表示したいタイミングでtocbot.refresh()を呼び出す必要があります。

まとめ

Tocbotは、目次を自動的に生成するための軽量なライブラリです。
この記事では、ReactとTypeScriptでTocbotを使用する方法を紹介しました。

スタイリングもかなりしやすいので、さくっと目次を作りたい場合はおすすめです。

注意点ですが、クライアントサイドで動作するため、SEOには向いていないです。
クローラーに目次を認識させたい場合は、あらかじめサーバーサイドで生成しておく必要があります。

私は、目次によるSEO向上はそこまで考えていないので、このまま使おうと思います。

それでは、また👋

profile

ハルミ

1997年生まれ。某メーカーの新米DX担当。
三度の飯より効率化が好き。
プログラミングにハマり、Webエンジニアを目指す。
現在React/Next.jsを学習しています🚀