JavaScript

プラグインなしで目次を自動生成する【WordPress・JavaScript】

2022/05/24

先日、サイトデザインを大きくリニューアルしました!

1年半ほど前に自作したWordPressテーマで運営してまいりましたが、自身の成長に伴ってサイトの改善部分が見えてきたため、改修作業に踏み切りました。

今回の作業で多くの知見を得ることができましたので、できるだけ多くの記事にして発信できるように努めていく所存です。

まずはWordPressで記事を作成した際の「目次の自動生成」についてまとめましたので、ぜひご覧になっていってください。

目次を生成する方法

WordPressの自作テーマを利用している場合、目次を作成する方法は主に3種類です。

エディタ画面で毎回設定する

今まではこの方法で目次を作っていました。

この場合だと毎回手入力でリンクを設定する必要があり、非常に面倒な作業になります。

また、手入力である分ミスが発生するリスクがあります。

プラグインを利用する

見出しを検知して目次を自動生成してくれるプラグインもあります。

しかし、プラグインを使うほどサイトは重くなっていきます。

極端な話、目次生成するプラグインだけを利用するサイトであれば問題ないかもしれませんが、実際にはそのようにならない場合が多いと思いますので、できれば自作したいところです。

また、既存のプラグインではデザインが限られます。

サイトのデザインや構成に合わない場合は使うことが難しくなります。

自作の目次生成プログラムを書く

自分でプログラムを書くことにより、目次を自動生成できるようにする方法です。

この方法であれば見た目や機能を自由にアレンジすることができる上に、作業の効率化を図ることができます。

ただ、WordPress自体はPHPという言語を基に作られているので、方法を検索してもPHPで書く方法が出てきてしまいます。

そこで、昨今のフロントエンドエンジニア、並びにWeb制作の勉強をしている人が習学している可能性が高いであろうJavaScriptを使ってこの機能を実装してみます。

実装

今回実装した機能は以下の通りです。

・エディタに書いている見出しのh2とh3を自動で検知して目次にする
・最初のh2の見出しの前に目次を自動生成する
・h2を見出し、h3を小見出しとして、レイアウト位置をずらして生成されるようにする
・目次にページ内リンクをつけて記事の見出し部分に飛ぶようにする
・右側サイドバーにも目次を自動生成する
・サイドバーの目次はスクロールすると固定される
・サイドバーの目次の見出しが多くて画面をはみ出してしまう場合、自動で画面内に収まるように表示され、スクロールされる機能がつく

上記の条件を踏まえ、機能を実装していきました。

以下はコード全体です。

//目次自動生成
const heading = document.querySelectorAll('h2, h3');
const createTocArea = document.createElement('ol');
createTocArea.className = 'toc_area';
const tocHeader = document.createElement('p');
tocHeader.className = 'toc_header';
tocHeader.innerText = '目次';

let i = 0;
while (i < heading.length) {
  if (heading[i].tagName === 'H2') {
    const tocTitleH2 = document.createElement('li');
    tocTitleH2.className = 'toc_title_h2';
    tocTitleH2.innerHTML = `<a class="toc_link" href="#${i}">${heading[i].innerText}</a>`;
    createTocArea.appendChild(tocTitleH2);
  } else if (heading[i].tagName === 'H3') {
    const tocTitleH3 = document.createElement('li');
    tocTitleH3.className = 'toc_title_h3';
    tocTitleH3.innerHTML = `<a class="toc_link" href="#${i}">${heading[i].innerText}</a>`;
    createTocArea.appendChild(tocTitleH3);
  }
  const tocLinks = document.createElement('div');
  tocLinks.setAttribute('id', `${i}`);
  tocLinks.setAttribute('class', 'anchor');
  heading[i].appendChild(tocLinks);
  i++;
}

heading[0].before(createTocArea);
createTocArea.before(tocHeader);

const sidebarTocContents = document.querySelector('.sidebar_toc');

let j = 0;
while (j < heading.length) {
  if (heading[j].tagName === 'H2') {
    const sidebarTocTitleH2 = document.createElement('li');
    sidebarTocTitleH2.className = 'sidebar_toc_title_h2';
    sidebarTocTitleH2.innerHTML = `<a class="toc_link" href="#${j}">${heading[j].innerText}</a>`;
    sidebarTocContents.appendChild(sidebarTocTitleH2);
  } else if (heading[j].tagName === 'H3') {
    const sidebarTocTitleH3 = document.createElement('li');
    sidebarTocTitleH3.className = 'sidebar_toc_title_h3';
    sidebarTocTitleH3.innerHTML = `<a class="toc_link" href="#${j}">${heading[j].innerText}</a>`;
    sidebarTocContents.appendChild(sidebarTocTitleH3);
  }
  j++;
}

上記のJavaScriptが実行されることで、以下のようなHTMLが目次として生成されるイメージです。

<p class="toc_header">目次</p>
<ul class="toc_area">
  <li class="toc_title_h2">
    <a class="toc_link" href="#番号">見出し</a>
  </li>
  <li class="toc_title_h3">
    <a class="toc_link" href="#番号">小見出し</a>
  </li>
</ul>

生成されたHTMLを想定して、あらかじめCSSでスタイルを当てておけば完成です。

注意点

ここからは細かい注意点について説明していきます。

olタグ

const createTocArea = document.createElement('ol');

このリスト部分はよく見かけるulタグではなくolタグを採用しています。

単純な並びのリストである場合はulタグであり、数列や序列を伴っているリストにはolタグが使われるそうです。

イメージとしては中黒である「・」で連ねるリストがulタグであり、1,2,3…といった順番があるものはolタグです。

目次は上から順番を表しているので、olタグを使うそうです。

テンプレートリテラル

tocTitleH2.innerHTML = `<a class="toc_link" href="#${i}">${heading[i].innerText}</a>`;

生成されるaタグの中身はテンプレートリテラルで記述することで解決しています。

テンプレートリテラルの記述

・通常の文字列を表すクオーテーションではなくバッククオートで全体を囲み、その中身HTMLを記述
・置き換える変数をドルマークと波括弧で囲む

サイト内リンク

const tocLinks = document.createElement('div');
  tocLinks.setAttribute('id', `${i}`);
  tocLinks.setAttribute('class', 'anchor');
  heading[i].appendChild(tocLinks);

上記の記述をすることで、元々ある見出しの直下にidを持ったdivが生成されます。

これで生成された目次をクリックすることで見出しまで飛ぶことができますが、このままでは見出しのちょっと先まで移動してしまいます。

CSSには以下のような記述を書き足します。

display: block;
padding-top: 70px;
margin-top: -70px;

paddingとmarginの値を修正することで、見出しに飛んだときの位置を調整することができます。

ぴったりの位置になるまで数値を調整してみてください。

サイドバーの見出し

変数sidebarTocContents以下はサイドバーにも同じ見出しを生成させる記述です。

このままでも機能しますが、より便利にするためにCSSに記述をしていきます。

まずは、「スクロールしてもそのまま画面に固定してくれる設定」にするために、サイドバーを囲っているwrapper部分のCSSにposition: stickyを記述します。

sidebar-wrapper {
  position: sticky;
  top: 10px;
}

画面の一番上に固定されると見にくいので、top: 10px;をあてています。

qiitaなどがこの目次を採用しています。

https://qiita.com/

一見これで問題ないように見えますが、position: stickyにした場合では大量に目次が生成されるような長文の記事を書いてしまった場合、「目次が多すぎて画面からはみ出てしまう」という現象が発生します。

このような場合にはolタグにCSSを記述します。

ol {
  overflow: auto;
  max-height: 70vh;
}

これで大量の目次が発生した場合にはスクロールに切り替わるようになりました。

overflow: scroll;でも問題なく動きます。

注意点は以上です。

JavaScriptファイルの読み込み

最後にJavaScriptの読み込みについてです。

WordPressに関してはヘッダーやフッターに<script src=”〇〇”>と書いてjsファイルを読み込ませるのではなく、functions.phpに読み込み専用の記述をしてjsファイルを読み込ませるようです。

参考にさせていただいたサイトは以下になりますので、自身のサイト構造に合わせて書き換えて使ってみてください。

https://sterfield.co.jp/designer/wordpress%E3%81%A7%E3%81%AE%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%94%E3%81%A8%E3%81%AEcss%E3%82%84js%E3%81%AE%E8%AA%AD%E3%81%BF%E8%BE%BC%E3%81%BF%E3%81%AE%E6%8C%AF%E3%82%8A%E5%88%86%E3%81%91%E3%82%92functi/

上記のリンクではトップページや記事ページなど、ページごとに違うjsファイルを読み込ませる方法を解説してくれています。

必要のないページにjsファイルを読み込ませるとエラーが発生するので、今回のような目次生成であれば記事ページ(WordPressであればsingle.phpと呼ばれるファイル)だけに読み込ませるようにしてみてください。

以上、目次を自動生成する方法でした。

おすすめ記事