【WordPress】目次をプラグインなしで自作する方法【自動生成|希望する位置に自動挿入|スクロールアニメーション|固定ヘッダーの高さを考慮】

WordPressで目次を自作する方法を紹介します。

自作の目次は、functions.phpにコードを2つ(目次生成とスクロールアニメーション)、single.php等のテンプレートファイルにコードを数行挿入する(目次表示)ことで実装できます。

目次

今回実装するの目次の機能

今回紹介する目次に実装する機能は下記のとおりです。

目次に実装する機能
  • 目次自動生成:H2タグが2つ以上ある場合に目次を自動生成する。
  • 目次自動挿入:希望する任意の位置に目次を自動挿入できる。あらゆるテンプレートファイルに利用可能。例)single.php, single-$posttype.php, page.php, page-$slug.phpなどの希望の位置に直接目次コードを書き込むことができる。
  • スクロールアニメーション:目次項目クリックでアンカー位置までスクロールアニメーションで移動。
  • 固定ヘッダーの高さを考慮:アンカー先に移動する時、固定ヘッダーがあると隠れてしまいます。その対策として、固定ヘッダーの高さをpx単位で予め指定することで、アンカー上部に余白を残して移動します。

この方法は、WordPressの the_contentフィルターを通じて処理されるコンテンツ(主に投稿やページの本文)に対してのみ機能します。直接、テンプレートファイルにコーディングした見出しには対応できません。

コード1:目次の自動生成(functions.php)

実装する機能
  • 「目次」というテキストをタイトルとして使用
  • 目次全体を囲うタグ(<div class=”toc_wrapper”>)を設置
  • コンテンツ内かH2タグを検出
  • 目次に項目を追加
  • H2タグのテキストからアンカー名を生成(ただし、空白は-に変換、大文字は小文字に変換)
  • コンテンツ内のH2タグにアンカーを追加
  • H2タグが2つ以下の場合は目次を表示しない

functions.phpに目次を自動生成するための下記コードをコピペして下さい。

/*----------------------------------------------------------*/
/* 目次自動生成 */
/*----------------------------------------------------------*/
function custom_generate_table_of_contents($content) {
  $toc_content = '<h2>目次</h2><ul>';
  $pattern = '/<h2 class="(wp-block-heading)">(.*?)<\/h2>/i';
  $content_with_anchors = $content;

  if (preg_match_all($pattern, $content, $matches, PREG_OFFSET_CAPTURE)) {
      // H2タグが2つ以上ある場合のみ目次を生成
      if (count($matches[2]) > 1) {
          $offset = 0;
          foreach ($matches[2] as $index => $match) {
              $anchor = strtolower(str_replace(' ', '-', $match[0]));
              $toc_content .= '<li><a href="#' . $anchor . '">' . $match[0] . '</a></li>';
              $anchor_tag = '<h2 class="' . $matches[1][$index][0] . '" id="' . $anchor . '">' . $match[0] . '</h2>';
              $position = $matches[0][$index][1] + $offset;
              $content_with_anchors = substr_replace($content_with_anchors, $anchor_tag, $position, strlen($matches[0][$index][0]));
              $offset += strlen($anchor_tag) - strlen($matches[0][$index][0]);
          }
          $toc_content .= '</ul>';
          // 目次の外枠を追加
          $toc = '<div class="toc_wrapper">' . $toc_content . '</div>';
      } else {
          // H2タグが1つ以下の場合は空の文字列を返す
          $toc = '';
      }
  } else {
      // H2タグが見つからない場合も空の文字列を返す
      $toc = '';
  }

  return ['toc' => $toc, 'content' => $content_with_anchors];
}

5行目のH2タグを検出するための正規表現パターンでは、今回はsingle.phpのブロックエディターで挿入したH2タグに自動付与されるclass属性「wp-block-heading」を使っています。

上手く動作しない時はChromeのデベロッパーツールなどで対象とするclass属性を確認してみて下さい。

補足:2種類以上のclass属性のh2タグを対象とするとき

今回は、投稿タイプ(single.php)およびカスタム投稿タイプ(single-$posttype.php)のブロックエディターで挿入したh2見出しに自動付与されるclass属性「wp-block-heading」のみを対象しているために下記のようにコード内で記述しています。

// H2タグを検出するための正規表現パターン
$pattern = '/<h2 class="wp-block-heading">(.*?)<\/h2>/i';

これに対して、例えば、class属性page_h2(任意)のh2タグも対象としたい場合には下記のようにコードを書くことで対応可能です。`|`は「または」を意味する正規表現の特殊文字です。

// H2タグを検出するための正規表現パターン:class属性2つ以上
$pattern = '/<h2 class="(wp-block-heading|page_h2)">(.*?)<\/h2>/i';

class属性が3つ以上に増えた場合も同様に対応可能です。

コード2:スクロールアニメーション(JavaScript)

実装する機能
  • アンカー移動に上部に余白を持たせる:px単位で調整可能
  • アンカーを移動時にスクロールアニメーション

script.js等のスクリプトファイルに下記コードをコピペして下さい。

/*----------------------------------------------------------*/
// 目次機能オプション
// ・アンカー移動時にスクロールアニメーション
// ・アンカー移動先に対して固定ヘッダーを考慮
/*----------------------------------------------------------*/
document.addEventListener('DOMContentLoaded', function () {
  var headerOffset = 100; // 固定ヘッダーの高さに応じて調整
  document.querySelectorAll('.toc_wrapper a').forEach(function (link) { // 目次リンクのCSSセレクター
      link.addEventListener('click', function (e) {
          e.preventDefault();
          var targetId = link.getAttribute('href').substring(1);
          var targetElement = document.getElementById(targetId);
          if (targetElement) {
              // スクロール位置を計算(固定ヘッダーの高さを考慮)
              var topPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - headerOffset;
              // スムーズスクロールを実行
              window.scrollTo({
                  top: topPosition,
                  behavior: 'smooth'
              });
          }
      });
  });
});

上のコードでは2行目の下記コードでアンカー移動時の上部余白量を調整しています。今回は100pxとしていますが、状況に応じて変更して下さい。

var headerOffset = 100; // 固定ヘッダーの高さに応じて調整

補足:3行目で目次リンクのセレクターを指定しますが、ここで半角スペースを使った子孫セレクターで絞り込み指定している理由はWordPressの投稿ページのコードを事例として使っているからです。WordPressの投稿ページで作った目次リストのulタグでは、class属性などのCSSセレクターを自分で付与できないので、子孫セレクターを使っているわけです。これが固定ページなどでコードを自作できる場合には、自分で書いた目次リンクのセレクター(aタグのclass属性等)を普通に使うことができます。

document.querySelectorAll('.toc_wrapper a').forEach(function (link) {

セレクター名が異なる複数の目次でスクロールアニメーションを動作させたい時

複数のページ且つセレクター名が異なる場合には、上記コードで複数セレクターを指定する必要があります。それを変数を使って書き直したコードが下記です。

/*----------------------------------------------------------*/
// 目次機能オプション
// ・アンカー移動時にスクロールアニメーション
// ・アンカー移動先に対して固定ヘッダーを考慮
/*----------------------------------------------------------*/
document.addEventListener('DOMContentLoaded', function () {
  var headerOffset = 50; // 固定ヘッダーの高さに応じて調整
  // 両方の目次に対するセレクターを指定
  var selector = '.toc_wrapper a, .privacy-table-contents_wrapper a';
  document.querySelectorAll(selector).forEach(function (link) {
      link.addEventListener('click', function (e) {
          e.preventDefault();
          var targetId = link.getAttribute('href').substring(1);
          var targetElement = document.getElementById(targetId);
          if (targetElement) {
              // スクロール位置を計算(固定ヘッダーの高さを考慮)
              var topPosition = targetElement.getBoundingClientRect().top + window.pageYOffset - headerOffset;
              // スムーズスクロールを実行
              window.scrollTo({
                  top: topPosition,
                  behavior: 'smooth'
              });
          }
      });
  });
});

3行目の変数で、複数のセレクターを指定しています。複数セレクターは,で区切ればOKです。

  // 両方の目次に対するセレクターを指定
  var selector = '.toc_wrapper a, .privacy-table-contents_wrapper a';

注意点:このJSのセレクターですが、「固定ページのページ内リンクボタン」と「アーカイブページのカテゴリーリンクボタン」でHTMLとCSSを共用している場合、上のコードをそのまま使うとカテゴリーアーカイブへのリンクが無効化されますので注意して下さい。カテゴリーリンクでは、少なくとも上のJSで使っているセレクターとは別のClass属性にして下さい。

コード3:目次を挿入(テンプレートファイル: single.phpなど)

下記コードを目次を挿入したテンプレートファイル(single.phpなど)に直接書いて下さい。

<!-- 目次を出力 -->

<?php
// 目次とアンカーが追加されたコンテンツを取得
$toc_and_content = custom_generate_table_of_contents(get_the_content());
// 目次とアンカーを出力
echo $toc_and_content['toc']; ?>

<!-- アンカーが追加されたコンテンツを出力 -->
<?php echo apply_filters('the_content', $toc_and_content['content']); ?>

上のコードには2つの機能がありますので、機能別に使い方を説明していきます。

目次を出力

目次出力に関するコードをテンプレートファイル内の目次を挿入したい位置に直接書いて下さい。

<!-- 目次を出力 -->
<?php
// 目次とアンカーが追加されたコンテンツを取得
$toc_and_content = custom_generate_table_of_contents(get_the_content());
// 目次とアンカーを出力
echo $toc_and_content['toc']; 
?>

目次生成プラグイン等では、目次はコンテンツ内の先頭見出しの上が一番上位になりますが、この方法を使うとさらの上の位置に目次を挿入できます。例えば、タイトル(H1)とサムネイルの間に目次を表示させたい場合にはプラグインでは困難だと思います。

アンカーが追加されたコンテンツを出力

single.phpのループ内ではコンテンツを下記WordPress関数「the_content();」で呼び出しますが、

<?php the_content(); ?>

上記WordPress関数をそっくりそのまま下記のコードと差し替えて下さい。

<!-- アンカーが追加されたコンテンツを出力 -->
<?php echo apply_filters('the_content', $toc_and_content['content']); ?>

これにより、アンカーが追加されたコンテンツが出力されるようになり、目次のアンカーリンクが機能するようになります。

テンプレートファイル内の参考コード

下記に実際にsingle.phpのループ内に直接上記コードを書いた参考コードを載せておきます。

<?php if (have_posts()) : ?>
  <?php while (have_posts()) : the_post(); ?>

    <div class="single-title-upper_wrapper">
      <!-- カテゴリーのデータを取得 -->
      <?php
      // カテゴリーのデータを取得
      $cat = get_the_category();
      $cat = $cat[0];
      // カテゴリー名を取得
      $cat_name = $cat->cat_name;
      ?>
      <!--  カテゴリー名を出力 -->
      <?php echo esc_html($cat_name); ?>
    </div>
    <h1 class="c-single-h1"><?php the_title(); ?></h1>
    <!-- 投稿日表示 -->
    <div class="single-title-bottom_wrapper"><?php the_time('Y年n月j日') ?></div>

    <!-- 目次を出力 -->
    <?php
    // 目次とアンカーが追加されたコンテンツを取得
    $toc_and_content = custom_generate_table_of_contents(get_the_content());
    // 目次とアンカーを出力
    echo $toc_and_content['toc']; ?>

    <!-- サムネイル表示 -->
    <div class="single-thumbnail-wrapper">
      <?php the_post_thumbnail(); ?>
    </div>

    <!-- アンカーが追加されたコンテンツを出力 -->
    <?php echo apply_filters('the_content', $toc_and_content['content']); ?>

  <?php endwhile; ?>
<?php endif; ?>

目次のCSSカスタマイズ

以下に上記コードで生成された目次に対して、CSSをカスタマイズした参考コードを載せておきます。

// ------------------------------------
// 目次
// ------------------------------------
// 目次外枠
.toc_wrapper {
  padding: 20px 40px 40px;
  border: 5px solid fw.$gray-3;
  border-radius: 10px;
  margin: 50px 0;
  @include fw.mq("sp") {
    padding: 20px;
    margin: 30px 0;
  }
}
// 目次タイトル
.toc_wrapper h2{
  font-size: 2.4rem;
  font-weight: 600;
  line-height: 1.65;
  margin-bottom: 24px;
  @include fw.mq("sp") {
    font-size: 1.6rem;
    margin-bottom: 14px;
  }
}
// 目次 ulタグ
.single-post .l-main .l-section .toc_wrapper ul,
.single-case .l-main .l-section .toc_wrapper ul {
  padding: 0px;
  margin-bottom: 0px;
  background-color: fw.$white;
  list-style: none;

}
// リストマーカー無効化|必要なければ無視して下さい
.single-post .l-main .l-section .toc_wrapper li::after,
.single-case .l-main .l-section .toc_wrapper li::after {
  display: none;
}
// 目次項目のフォント太さと文字色
.single-post .l-main .l-section .toc_wrapper li a,
.single-case .l-main .l-section .toc_wrapper li a {
  font-weight: 600;
  color: fw.$text;
}
補足説明

上記CSSは、私のケースをそのまま載せたものですので、適宜変更して下さい。私の場合は投稿タイプのブロックエディターで挿入するulやolリストのCSSも自作していますので、上記コードのセレクター詳細度が複雑なものになっています。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

■清水WEB制作代表
■コーディング:WordPress(オリジナルテーマ制作等)・HTML・Sass・FLOCSS・JavaScript(jQuery)等
■集客力:YouTube/Instagram/ブログでそれぞれ登録者数16000人/フォロワー13000人/月間最大アクセス50000PVの集客実績があります
■文章作成:博士号所有、会社員時代は科学雑誌に寄稿していたので文章作成も得意です
■写真技術:Amazon Kindle出版で、写真集・撮影編集解説書を5冊好評発売中です

目次