サイトアイコン

toLog

WordPressにて目次機能をJavaScriptで自動生成

  • 更新日:
  • 投稿日:
サムネイル

この記事は最終更新日から3年以上が経過しています。

はじめに

現在、当ブログは WordPress ではなく Gatsby.js で構築されています 🙇‍♂️

Qiita や Classmethod などのテック系ブログを見ていると画面サイズが HD 以上になるとサイドバーに現れる「目次」が常々良いなと思っていました。自分でも同じことができないかと思い、WordPress ならプラグインがあるだろうと考えたのですが、案の定良い感じのがない、、、

それなら、自作するしかない。

今回は下記の観点で「目次機能」を自作しました。

  • WordPress だけでなく、他の CMS / フレームワークでも扱えるように JavaScript で実装
  • 記事中の見出し ( Heading ) を検索して、目次を自動生成
  • ページ内リンクを活用することでユーザビリティ向上を目論む

tl; dr

codepen で簡単なサンプルを作っています。ご参考程度に見てもらえればです。

https://codepen.io/canji53/pen/OJyoaRM

環境

  • PHP 7.3
  • WordPress 5.3
  • Gutenberg エディタ

ポイント

ページ内リンクについて

html5 であれば下記のように組めばページ内をジャンプするリンクを作成できます。

詳しくはコチラの SEO ラボさんの記事も参考にしていただければです。

1<a id="title">ジャンプ先</a>
1<a href="#title">ジャンプ元</a>

目次作成の流れ

今回組んだ JavaScript はざっくりと下記のような流れになります。

1. 見出しにサイト内リンクを追加

  • 記事内の見出し H2、H3、H4 のそれぞれのレベル要素を取得
    • Gutenberg では、見出しブロックが H2、H3、H4 となるため
  • 取得した要素ごとに見出し名を id として追加
1const getHeadingElementList = () => {
2  const postElement = document.getElementById("post");
3  return postElement.querySelectorAll(&#91;"h2", "h3", "h4"]);
4};
5
6const addInternalLinksForHeading = () => {
7  const headingElementList = getHeadingElementList();
8  Array.prototype.forEach.call(headingElementList, (headingElement) => {
9      headingElement.innerHTML = `&lt;a id="${headingElement.textContent}">` + headingElement.innerHTML + "&lt;/a>";
10  });
11};
12
13addInternalLinksForHeading();

2. 見出しのレベルに応じて目次を生成

  • 目次を挿入したい要素を予め取得、ここでは contents とする
  • 記事内の見出し H2、H3、H4 要素を取得
  • 前ステップで取得した要素をループ処理へ
  • H2、H3、H4 のレベルに応じて ul li タグの深さが異なる連鎖要素を用意
    • #id を a タグで追加することでサイト内リンクを実現
  • 前ステップで取得した要素を contents の後列に順次追加
  • ループ処理終了
1const getFirstLevelList = (internalLinkId) => {
2  return `
3    <ul>
4      <a href="#${internalLinkId}">
5        <li>
6          ${internalLinkId}
7        </li>
8      </a>
9    </ul>
10  `;
11};
12
13const getSecondLevelList = (internalLinkId) => {
14  return `
15    <ul>
16      <li>
17        <ul>
18          <a href="#${internalLinkId}">
19            <li>
20              ${internalLinkId}
21            </li>
22          </a>
23        </ul>
24      </li>
25    </ul>
26  `;
27};
28
29const getThirdLevelList = (internalLinkId) => {
30  return `
31    <ul>
32      <li>
33        <ul>
34          <li>
35            <ul>
36              <a href="#${internalLinkId}">
37                <li>
38                  ${internalLinkId}
39                </li>
40              </a>
41            </ul>
42          </li>
43        </ul>
44      </li>
45    </ul>
46  `;
47};
48
49const addContents = () => {
50  const contentsElement = document.getElementById("contents");
51  const headingElementList = getHeadingElementList();
52
53  Array.prototype.forEach.call(headingElementList, (headingElement) => {
54    const headingAnchorElement = headingElement.getElementsByTagName("a")[0];
55
56    if (headingElement.tagName === "H2") {
57      var contentsLine = getFirstLevelList(headingAnchorElement.id);
58    }
59    if (headingElement.tagName === "H3") {
60      var contentsLine = getSecondLevelList(headingAnchorElement.id);
61    }
62    if (headingElement.tagName === "H4") {
63      var contentsLine = getThirdLevelList(headingAnchorElement.id);
64    }
65
66    contentsElement.insertAdjacentHTML("beforeend", contentsLine);
67  });
68};
69
70addContents();

3. シンプルにスタイル調整

ul li タグのスタイル調整では、主に paddingmargin 周りをいじって「目次感」を再現するのが肝かと思います。

また、サイトデザインにもよるのですが、Qiita 等の技術系のブログではサイドバーに目次が画面追従するように実装されているため、これを position: sticky で実現するのが尚良しかと思います。

1#contents {
2  position: sticky;
3  top: 25%;
4}
5
6#contents ul {
7  padding-inline-start: 0rem;
8}
9#contents ul li ul,
10#contents ul li ul li ul {
11  padding-inline-start: 0.9rem;
12}
13
14#contents ul li,
15#contents ul li ul li,
16#contents ul li ul li ul li {
17  padding: 0 0.5rem;
18  border-radius: 0.3rem;
19  overflow: scroll;
20}
21
22#contents ul,
23#contents ul li ul,
24#contents ul li ul li ul {
25  margin-bottom: 0;
26  list-style-type: none;
27}
28
29#contents ul a {
30  color: #888;
31  text-decoration: none;
32}

おわりに

わずかなコード量ですが、目次機能を実装するのに意外と時間がかかりました。フロント側の勉強もなお一層必要だなと痛感しました。

また、そこそこ重要なのは CSS 側でのスタイル調整かと思われます。ところが、コチラはお使いの環境によって大きく変わってくるため、時間のかかる部分かもしれないです。当ブログでは、画面幅が 1280px 以上で右側のサイドバーに目次機能が現れますが、基本的には Qiita に近い形で調整しています。

参考文献


プロフィール画像

canji

とにかく私的にサービスを作りたい発作を起こしている。お腹はペコペコ。

  • toLog Tools icon
  • dots icon