記事の構造を明確にするため、多くのブログ記事やWebページでは、Hタグをタイトルに使用して各セクションを示していると思います。
この記事では、WordPressで記事で使用されているH3とH4タグを自動的に目次に変換する方法を紹介します。
目次がH3とH4のタグになっているのはヘッダーやブログのタイトルなどでH1やH2を使用している事が多い(自分のサイトはそうなっている)からです。
ここは必要に応じてご自身のサイトで使用しているタグに変更してください!

PHPコードの準備

まず、WordPressのfunctions.phpに以下のコードを追加します。
このコードは、Hタグのタイトルを基にして目次を生成し、それぞれのヘッダーにユニークなIDを割り当てます。
H3タグ、H4タグがある場合は階層にして表示されます。

// ショートコードで目次を生成する関数
function generate_content_table_of_contents_shortcode($atts) {
    global $global_header_index;  // グローバル変数の使用
    $content = get_the_content();
    $header_index = $global_header_index;  // ここでグローバル変数から値を取得
    preg_match_all('/(<h([3-4]{1})[^>]*>)\s*(.*)<\/h\2>/msU', $content, $matches, PREG_SET_ORDER);
    $toc = '<div class="toc-container">';
    $toc .= '<h2 class="m-0">目次</h2>';
    if ($matches) {
        $toc .= '<ol class="toc">';
        $current_depth = 0;
        $is_open_h3 = false;
        $index = 0; // ここでインデックスを初期化
        foreach ($matches as $match) {
            $depth = (int)$match[2];
            $title = strip_tags($match[3]);
            $id = 'id-' . md5($title) . '-' . $header_index; 
            $header_index++;
            if ($depth === 3 && $current_depth === 4) {
                $toc .= '</ol></li>'; 
            }
            if ($depth === 3 && $current_depth === 3 && $is_open_h3) {
                $toc .= '</li>'; 
            }
            if ($depth === 3) {
                $toc .= "<li><a href='#{$id}'>{$title}</a>"; 
                $is_open_h3 = true;
            }
            if ($depth === 4 && $current_depth === 3 && $is_open_h3) {
                $toc .= '<ol>';  
            }
            if ($depth === 4) {
                $toc .= "<li><a href='#{$id}'>{$title}</a></li>"; 
            }
            $current_depth = $depth;
        }
        if ($current_depth === 4) {
            $toc .= '</ol></li>'; 
        } elseif ($current_depth === 3 && $is_open_h3) {
            $toc .= '</li>'; 
        }
        $toc .= '</ol>'; 
    }
    $toc .= '</div>';
    return $toc;
}
add_shortcode('table_of_contents', 'generate_content_table_of_contents_shortcode');
function replace_first_occurrence($search, $replace, $subject) {
    $pos = strpos($subject, $search);
    if ($pos !== false) {
        $subject = substr_replace($subject, $replace, $pos, strlen($search));
    }
    return $subject;
}
// 各HタグにユニークなIDを追加する関数
function add_ids_to_headers($content) {
    global $global_header_index;  // グローバル変数の宣言
    $header_index = (int) get_transient('header_index');
    $global_header_index = $header_index;  // グローバル変数に保存
    $pattern = '/(<h([3-4]{1})[^>]*>)\s*(.*)<\/h\2>/msU';
    preg_match_all($pattern, $content, $matches, PREG_SET_ORDER);
    foreach ($matches as $match) {
        $tag = $match[1];
        $title = strip_tags($match[3]);
        // md5を使用してユニークなIDのベースを生成し、インデックスを追加する
        $id = 'id-' . md5($title) . '-' . $header_index;
        $header_index++;

        $new_tag = "<h{$match[2]} id='{$id}'>";
        $content = replace_first_occurrence($tag, $new_tag, $content);
    }
    // インデックスを記事のメタデータとして保存
    update_post_meta($post_id, 'header_index', $header_index);
    return $content;
}
add_filter('the_content', 'add_ids_to_headers');

JSコードの導入

次に、スムーズスクロール機能を追加するためのJavaScriptを導入します。以下のコードをテーマのfooter.phpに直接追加するか、別のJSファイルとして読み込ませます。

    document.querySelectorAll('a[href^="#"], .pagelink').forEach(e => {
        const href = e.getAttribute("href");

        // ページ内リンク以外は処理をスキップ
        if (!href || href == '#' || href.indexOf('#') != 0) return;

        e.addEventListener('click', event => {
            event.preventDefault();

            // セレクタをデコードしてエスケープする
            let selector = decodeURIComponent(event.currentTarget.hash);
            if (selector.match(/^#(\d)/)) {
                selector = selector.replace(/^#/, '#\\3' + selector.charAt(1) + ' ');
            }

            const targetEl = document.querySelector(selector);

            if (targetEl) { 
                const rectTop = targetEl.getBoundingClientRect().top;
                const offsetTop = window.pageYOffset;
                const buffer = 100;
                const top = rectTop + offsetTop - buffer;

                window.scrollTo({
                    top,
                    behavior: "smooth"
                });
            } else {
                console.error(`Element for selector "${selector}" not found!`);
            }
        });
    });

目次の表示

WordPressのブロックエディターの記事やページで目次を表示したい場所に[table_of_contents]というショートコードを追加するだけです。
これでブログ記事の任意の場所に手軽に目次を設置することが出来ます。
見た目を整えたい方は任意のクラス名を付けてCSSを変更してみてください。

まとめ

このガイドを使用すると、WordPressの記事やページのH3、H4タグを自動的に目次に変換できます。
これにより、読者は記事の概要をすぐに把握し、特定のセクションにすばやくジャンプすることができます。
最初の設定は少し面倒かもしれませんが、一度設定してしまえば次回以降はショートコードを設置するだけで目次を設置できるのでブログを書く手間が格段に減ると思います。
あまりプラグインを使いたくない!って方は参考にどうぞ。
コードのカスタマイズや改善のアイディアがあれば、ぜひシェアしてください!