今回は、WordPressブログに目次を自動的に追加する機能を、プラグインを使わずに実装する方法をご紹介します。
目次があれば読者がコンテンツを把握しやすくなり、長い記事でも読みたいセクションにすぐジャンプできるようになります。
目次
実装方法の概要
今回の目次機能は大きく2つのステップで実装します。
- 記事内のh2タグに自動的にIDを付与する
- それらのh2タグを元に目次を生成する
では、実際のコードを見ながら詳しく解説していきましょう。
ステップ1:h2タグに自動でIDを追加する
まず、functions.php
に以下のコードを追加します。
// ▼ h2タグに自動でIDを追加するフィルター
function add_id_to_h2_tags($content)
{
$count = 0;
// 属性付き<h2>にマッチするように修正
$content = preg_replace_callback(
'/<h2([^>]*)>(.*?)<\/h2>/i',
function ($matches) use (&$count) {
$count++;
$id = 'toc-h2-' . $count;
// id属性がすでにある場合はスキップ(オプション)
if (strpos($matches[1], 'id=') !== false) {
return '<h2' . $matches[1] . '>' . $matches[2] . '</h2>';
}
// <h2 class="xxx"> に id を追加
return '<h2' . $matches[1] . ' id="' . $id . '">' . $matches[2] . '</h2>';
},
$content
);
return $content;
}
add_filter('the_content', 'add_id_to_h2_tags');
このコードは、投稿コンテンツ内のすべてのh2タグにユニークなIDを自動的に追加します。
add_id_to_h2_tags
関数は、WordPressのthe_content
フィルターで呼び出されます。これにより、コンテンツが表示される前に実行されます。preg_replace_callback
関数を使って、h2タグを正規表現で検索します:'/<h2([^>]*)>(.*?)<\/h2>/i'
というパターンは、属性を持つh2タグも含めて全てのh2タグにマッチします。$matches[1]
にはタグの属性部分(例:class="title"
)が入ります。$matches[2]
にはタグの内容(見出しテキスト)が入ります。
- コールバック関数で、各h2タグを処理します:
- カウンター変数を増やし、
toc-h2-1
、toc-h2-2
のように連番のIDを作成します。 - すでにID属性が付いている場合はスキップするチェックを行います(任意)。
- 元のh2タグの属性を保持しつつ、新しいID属性を追加します。
- カウンター変数を増やし、
ステップ2:目次を生成する関数
次に、h2タグから目次を生成する関数を作成します。
// ▼ h2タグから目次を生成する関数
function generate_toc_from_h2($content)
{
if (is_singular('information')) {
// <h2>をすべて抽出
preg_match_all('/<h2.*?>(.*?)<\/h2>/i', $content, $matches);
// <h2>が1つもなければ、目次は表示しない
if (empty($matches[1])) {
return '';
}
// 目次HTMLを生成
$toc = '<div class="p-single__toc"><strong>目次</strong><ol class="p-single__tocList">';
$count = 0;
foreach ($matches[1] as $heading) {
$count++;
$id = 'toc-h2-' . $count;
$toc .= '<li class="p-single__tocItem"><a href="#' . $id . '">' . esc_html($heading) . '</a></li>';
}
$toc .= '</ol></div>';
return $toc;
}
return '';
}
この関数は、コンテンツ内のh2見出しを抽出し、それらへのリンクを含む目次を生成します。
is_singular('information')
で、特定の投稿タイプ(この場合は’information’)の単一ページでのみ目次を表示するように制限しています。必要に応じて、この条件を変更できます。preg_match_all
関数で、コンテンツから全てのh2タグを抽出します。$matches[1]
に全てのh2タグの内容(見出しテキスト)が配列として格納されます。
- h2タグが1つもない場合は、空の文字列を返して目次を表示しません。
- 目次のHTMLを構築します:
- div要素で目次全体をラップし、「目次」というタイトルを追加します。
- 順序付きリスト(ol)を使用して目次項目を表示します。
- 各h2見出しに対して、対応するIDにリンクするリスト項目を作成します。
esc_html()
関数で、見出しテキストをエスケープして安全に表示します。
ステップ3:テンプレートへの実装
最後に、生成した目次をテンプレートに表示します。この例では single-information.php
テンプレートを使用しています。
<!-- 本文 -->
<div class="p-single__content">
<?php
$content = apply_filters('the_content', get_the_content());
echo generate_toc_from_h2($content); // 目次を表示(h2があれば)
echo $content; // 本文を表示(h2にはIDが自動で付いてる)
?>
</div>
このコードは、コンテンツを取得し、目次を生成してから、IDが追加されたコンテンツを表示します。
apply_filters('the_content', get_the_content())
で、投稿の内容を取得し、the_content
フィルターを適用します。この時点で、先ほど定義したadd_id_to_h2_tags
関数によって、すべてのh2タグにIDが追加されています。generate_toc_from_h2($content)
で、h2タグから目次を生成し、表示します。- その後、ID付きのコンテンツ本文を表示します。
コードまとめ
functions.php
// ▼ h2タグに自動でIDを追加するフィルター
function add_id_to_h2_tags($content)
{
$count = 0;
// 属性付き<h2>にマッチするように修正
$content = preg_replace_callback(
'/<h2([^>]*)>(.*?)<\/h2>/i',
function ($matches) use (&$count) {
$count++;
$id = 'toc-h2-' . $count;
// id属性がすでにある場合はスキップ(オプション)
if (strpos($matches[1], 'id=') !== false) {
return '<h2' . $matches[1] . '>' . $matches[2] . '</h2>';
}
// <h2 class="xxx"> に id を追加
return '<h2' . $matches[1] . ' id="' . $id . '">' . $matches[2] . '</h2>';
},
$content
);
return $content;
}
add_filter('the_content', 'add_id_to_h2_tags');
// ▼ h2タグから目次を生成する関数
function generate_toc_from_h2($content)
{
if (is_singular('information')) {
// <h2>をすべて抽出
preg_match_all('/<h2.*?>(.*?)<\/h2>/i', $content, $matches);
// <h2>が1つもなければ、目次は表示しない
if (empty($matches[1])) {
return '';
}
// 目次HTMLを生成
$toc = '<div class="p-single__toc"><strong>目次</strong><ol class="p-single__tocList">';
$count = 0;
foreach ($matches[1] as $heading) {
$count++;
$id = 'toc-h2-' . $count;
$toc .= '<li class="p-single__tocItem"><a href="#' . $id . '">' . esc_html($heading) . '</a></li>';
}
$toc .= '</ol></div>';
return $toc;
}
return '';
}
archive.php
<!-- 本文 -->
<div class="p-single__content">
<?php
$content = apply_filters('the_content', get_the_content());
echo generate_toc_from_h2($content); // 目次を表示(h2があれば)
echo $content; // 本文を表示(h2にはIDが自動で付いてる)
?>
</div>