1. HOME
  2. WordPress
  3. プラグインを使わずにWordPressで目次を自動生成する方法
WordPress - 2025-07-23

プラグインを使わずにWordPressで目次を自動生成する方法

記事の冒頭に分かりやすい「目次」があると、ユーザーは内容を素早く把握でき、読みたいセクションへすぐに移動できます。
これはユーザー体験(UX)を向上させるだけでなく、Googleの検索結果にページ内リンクが表示される可能性を高めるなど、SEOの観点からも非常に重要です。

多くの目次生成プラグインが存在しますが、「サイトを重くしたくない」「HTMLやデザインを完全にコントロールしたい」という方も多いでしょう。

この記事では、テーマのfunctions.phpにコードを追加するだけで、プラグインを使わずに、以下のような高機能な目次を自動で生成する方法を徹底解説します。

  • H2〜H6見出しを自動で認識
  • 見出しの階層構造(親子関係)を完全に再現
  • クリックで開閉できる、折りたたみ機能付き

完成形のイメージと仕組み

実装すると、投稿ページ内の見出し(<h2><h3>など)を元に、記事の先頭に以下のような目次が自動で挿入されます。

<div class="toc">
  <strong class="toc-title">目次</strong>
  <ul>
    <li><a href="#toc-1">見出し2</a>
      <ul>
        <li><a href="#toc-2">見出し3</a></li>
      </ul>
    </li>
    <li><a href="#toc-3">別の見出し2</a></li>
  </ul>
</div>

この機能は、以下の3つの要素を連携させることで実現しています。

  1. PHP: 記事のコンテンツがデータベースから読み込まれ、ブラウザに表示される直前に内容をフィルタリング。正規表現で見出しタグをすべて探し出し、目次リストを生成し、元の見出しタグにはアンカー用のIDを付与します。
  2. JavaScript: 目次のタイトル部分がクリックされたら、目次リストを開いたり閉じたりする「折りたたみ機能」を実装します。
  3. CSS: 生成された目次を見やすく、使いやすくスタイリングします。

STEP 1: functions.phpに目次生成コードを追加する

まず、お使いのテーマのfunctions.phpファイルに、以下のPHPコードをコピー&ペーストします。これが目次生成の心臓部です。

<?php

// 投稿コンテンツをフィルタリングして目次を自動生成する
add_filter('the_content', 'generate_toc_from_content');

function generate_toc_from_content($content) {
    // 投稿ページ以外では処理を実行しない
    if (!is_single()) {
        return $content;
    }

    $matches = [];
    // h2からh6までの見出しを検索する正規表現
    $heading_pattern = '/<h([2-6])(.*?)>(.*?)<\/h\1>/i';

    // preg_match_allで見出しをすべて抽出し、オフセットも取得
    if (preg_match_all($heading_pattern, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
        $headings = [];
        $original_headings = [];
        $replaced_headings = [];

        foreach ($matches as $i => $match) {
            $level  = intval($match[1][0]);
            $text   = strip_tags($match[3][0]);
            $anchor = 'toc-' . ($i + 1);

            // 元の見出しタグ全体を保存
            $original_headings[] = $match[0][0];
            // IDを追加した新しい見出しタグを作成
            $replaced_headings[] = sprintf('<h%d id="%s"%s>%s</h%d>', $level, $anchor, $match[2][0], $match[3][0], $level);

            $headings[] = [
                'level'  => $level,
                'text'   => $text,
                'anchor' => $anchor
            ];
        }

        // 目次が2つ以上ある場合のみ生成
        if (count($headings) < 2) {
            return $content;
        }

        // 階層構造を持つ目次リスト(HTML)を生成
        $toc = '<div class="toc"><strong class="toc-title">目次</strong>';
        $toc .= build_hierarchical_toc_list($headings);
        $toc .= '</div>';

        // 元の見出しをID付きの見出しに一括で置換
        $content = str_replace($original_headings, $replaced_headings, $content);

        // 目次を記事コンテンツの先頭に追加
        $content = $toc . $content;

        // 折りたたみ用のJavaScriptをフッターに出力
        add_action('wp_footer', 'add_toc_toggle_script');
    }

    return $content;
}

// 階層構造の <ul> リストを生成する補助関数
function build_hierarchical_toc_list($headings) {
    $html = '';
    $last_level = 0;

    foreach ($headings as $heading) {
        $current_level = $heading['level'];

        if ($current_level > $last_level) {
            $html .= str_repeat('<ul>', $current_level - $last_level);
        } elseif ($current_level < $last_level) {
            $html .= str_repeat('</ul></li>', $last_level - $current_level);
        }

        $html .= '<li><a href="#' . esc_attr($heading['anchor']) . '">' . esc_html($heading['text']) . '</a>';

        $last_level = $current_level;
    }

    $html .= str_repeat('</li></ul>', $last_level);

    return $html;
}

// 折りたたみ用スクリプトを一度だけ出力するための関数
function add_toc_toggle_script() {
    // 既に出力済みの場合は何もしない
    if (did_action('wp_footer_toc_script') > 0) {
        return;
    }
    ?>
    <script>
    document.addEventListener('DOMContentLoaded', () => {
        const tocTitle = document.querySelector('.toc-title');
        const tocList = document.querySelector('.toc ul');

        if (tocTitle && tocList) {
            tocList.style.display = 'none'; // デフォルトで閉じる
            tocTitle.style.cursor = 'pointer';
            tocTitle.addEventListener('click', () => {
                const isHidden = tocList.style.display === 'none';
                tocList.style.display = isHidden ? 'block' : 'none';
            });
        }
    });
    </script>
    <?php
    // スクリプトが出力されたことを記録
    do_action('wp_footer_toc_script');
}

コードのポイント解説

  • add_filter('the_content', ...): WordPressの「フィルターフック」という機能です。投稿本文(the_content)が表示される直前に、自作関数(generate_toc_from_content)を割り込ませて内容を加工します。
  • preg_match_all(...): PHPの正規表現関数で、コンテンツ内からすべての見出しタグを根こそぎ探し出します。
  • build_hierarchical_toc_list(): このコードのキモとなる補助関数です。現在の見出しレベルと直前の見出しレベルを比較することで、<ul>タグを適切に開閉し、美しい階層構造を自動で作り上げます。
  • add_action('wp_footer', ...): こちらは「アクションフック」です。ページのフッター部分(wp_footer)に、目次を開閉するためのJavaScriptコードを安全に追加します。

STEP 2: CSSでデザインを整える

次に、目次の見た目を整えるためのCSSです。テーマのstyle.cssや、カスタマイザーの「追加CSS」などに以下のコードを追加してください。

/* 目次全体のコンテナ */
.toc {
  background: #f7f7f7; /* 背景色 */
  border: 1px solid #e0e0e0; /* 枠線 */
  border-radius: 5px; /* 角丸 */
  padding: 15px 20px;
  margin: 0 0 1.5em 0; /* 下に余白 */
}

/* 目次タイトル */
.toc-title {
  font-weight: bold;
  font-size: 1.1em;
  cursor: pointer; /* クリックできることを示すカーソル */
  display: block;
}

/* 目次リスト */
.toc ul {
  list-style-type: none; /* リストの黒点を非表示 */
  margin: 10px 0 0 0;
  padding-left: 0;
}

/* 階層のインデント */
.toc ul ul {
  margin-top: 5px;
  padding-left: 20px; /* 子要素はインデント */
}

.toc li {
  margin: 6px 0;
  font-size: 0.95em;
}

.toc a {
  text-decoration: none;
  color: #337ab7;
}

.toc a:hover {
  text-decoration: underline;
}

カスタマイズ

このコードは、あなたの好みに合わせて簡単にカスタマイズできます。

  • 目次タイトルを変更したい: generate_toc_from_content関数内の'<strong class="toc-title">目次</strong>'という部分を好きな文言に変更してください。
  • 目次をデフォルトで開いておきたい: add_toc_toggle_script関数内のtocList.style.display = 'none';の行を削除すればOKです。
  • H2とH3だけを目次に含めたい: generate_toc_from_content関数内の正規表現を'/<h([2-3])(.*?)>(.*?)<\/h\1>/i'のように変更します。
  • 見出しが3つ以上ないと目次を表示したくない: build_hierarchical_toc_listを呼び出す直前に、if (count($headings) < 3) { return $content; }のような条件分岐を追加します。

まとめ

プラグインに頼らず自作することで、サイトのパフォーマンスを最適に保ちながら、デザインの自由度を最大限に高めることができます。今回紹介したコードをベースに、ぜひあなたのサイトに最適な目次機能を実装してみてください。