WordPressの投稿詳細ページに、目次機能を追加する方法をご紹介いたします。
WordPressで目次機能といえば、「Table of Contents Plus」や「Rich Table of Contents」などのプラグインが有名ですが、今回はプラグインを使わない方法です。
WordPressにプラグインなしで目次機能を追加する方法
下記は、BLOG AND DESTROYさんの記事で紹介されていた方法です。コードの詳しい解説については、BLOG AND DESTROYさんの記事をご参照ください。
投稿詳細ページに目次機能を追加するには、functions.phpに以下を追記します。
function my_add_content( $content ) {
if ( is_single() ) {
// 属性を持たないh2・h3要素を正規表現で表すパターン
$pattern = '/<h[2-3]>(.*?)<\/h[2-3]>/i';
// 本文の中から、すべてのh2・h3要素を検索
preg_match_all( $pattern, $content, $matches, PREG_SET_ORDER );
// ページ内のh2・h3要素が3つ以上の場合に目次を出力
if( count( $matches ) > 3 ){
// 目次の出力に使用する変数
$toc = '<h2>目次</h2><ol>';
// 目次の階層の判断に使用する変数
$hierarchy = NULL;
// ループ回数を数える変数
$i = 0;
// 本文内のh2・h3要素を上から順番にループで処理
foreach( $matches as $element ){
// ループ回数を1加算
$i++;
// h2・h3に指定するIDの属性値を作成
$id = 'chapter-' . $i;
// h2・h3タグにIDを追加
$chapter = preg_replace( '/<(.+?)>(.+?)<\/(.+?)>/', '<$1 id ="' . $id . '">$2</$3>', $element[0] );
// ページ内のh2・h3要素を、IDが追加されているh2・h3要素に置換
$content = preg_replace( $pattern, $chapter, $content, 1);
// 現在のループで扱う要素を判断する条件分岐
if( strpos( $element[0], '<h2' ) === 0 ){
$level = 0;
}else{
$level = 1;
}
//現在の状態を判断する条件分岐
if( $hierarchy === $level ){ // h2またはh3がそれぞれ連続する場合
$toc .= '</li>';
}elseif( $hierarchy < $level ){ // h2の次がh3となる場合
$toc .= '<ol>';
$hierarchy = 1;
}elseif( $hierarchy > $level ){ // h3の次がh2となる場合
$toc .= '</li></ol></li>';
$hierarchy = 0;
}elseif( $i == 1 ){ // ループ1回目の場合
$hierarchy = 0;
}
// 目次の項目で使用する要素を指定
$title = $element[1];
// 目次の項目を作成。※次のループで<li>の直下に<ol>タグを出力する場合ががあるので、ここでは<li>タグを閉じていません。
$toc .= '<li><a href="#' . $id . '">' . $title . '</a>';
}
// 目次の最後の項目をどの要素から作成したかによりタグの閉じ方を変更
if( $level == 0 ){
$toc .= '</li></ol>';
}elseif( $level == 1 ){
$toc .= '</li></ol></li></ol>';
}
// 本文に目次を追加
$content = $toc . $content;
}
}
return $content;
}
add_filter( 'the_content', 'my_add_content' );本文内のH2要素とH3要素を抽出して目次が生成されます。生成された目次は「the_content()」の直前に追加されます。
目次のHTMLは以下のように出力され、各見出しにはid属性が追加されます。
<h2>目次</h2>
<ol>
<li>
<a href="#chapter-1">h2の見出し</a>
<ol>
<li><a href="#chapter-2">h3の見出し</a></li>
<li><a href="#chapter-3">h3の見出し</a></li>
<li><a href="#chapter-4">h3の見出し</a></li>
</ol>
</li>
<li>
<a href="#chapter-5">h2の見出し</a>
</li>
<li>
<a href="#chapter-6">h2の見出し</a>
</li>
</ol>
<!-- 本文ここから -->
<h2 id="chapter-1">h2の見出し</h2>
<h3 id="chapter-2">h3の見出し</h3>カスタム投稿に目次を追加したい場合は、2行目の「if ( is_single() )」を「if ( is_singular(‘news’) )」のように変更します。
また、上記だと見出しが4つ以上ある場合に目次が追加されますが、「count( $matches ) > 3」の部分で数を調整できます。
目次に出力するのがH2のみで良い場合
上記の例では、H2とH3を目次にしていますが、H2のみで良い場合はもっと簡略化できます。
function my_add_content($content) {
if (is_single()) {
$pattern = '/<h[2]>(.*?)<\/h[2]>/i';
preg_match_all($pattern, $content, $matches, PREG_SET_ORDER);
if (count($matches) > 3) {
$toc = '<h2>目次</h2><ol>';
$i = 0;
foreach ($matches as $element) {
$i++;
$id = 'chapter-' . $i;
$chapter = preg_replace('/<(.+?)>(.+?)<\/(.+?)>/', '<$1 id ="' . $id . '">$2</$3>', $element[0]);
$content = preg_replace($pattern, $chapter, $content, 1);
$title = $element[1];
$toc .= '<li><a href="#' . $id . '">' . $title . '</a></li>';
}
$toc .= '</ol>';
$content = $toc . $content;
}
}
return $content;
}
add_filter('the_content', 'my_add_content');H3を考慮しなくて良いのでだいぶスッキリしますね。
見出しが目次に反映されない場合
上記のコードだと英字のみの見出しだと目次に反映されませんでした。日本語以外の見出しがある場合にも対応したい場合は、下記コードをご参照ください。
H2とH3を対象に目次を生成
function my_toc($content) {
if (is_single()) {
$pattern = '/<(h2|h3)([^>]*)>(.*?)<\/(h2|h3)>/is';
preg_match_all($pattern, $content, $matches, PREG_SET_ORDER);
if (count($matches) > 1) {
$items = [];
$i = 0;
foreach ($matches as $element) {
$i++;
$tag = strtolower($element[1]);
$id = 'chapter-' . $i;
$original_heading = $element[0];
$new_heading = preg_replace(
'/<' . $tag . '([^>]*)>/i',
'<' . $tag . '$1 id="' . $id . '">',
$original_heading,
1
);
$content = preg_replace(
'/' . preg_quote($original_heading, '/') . '/',
$new_heading,
$content,
1
);
$title = trim(wp_strip_all_tags($element[3]));
$items[] = [
'tag' => $tag,
'id' => $id,
'title' => $title,
];
}
$toc_body = '';
$open_sub = false;
foreach ($items as $item) {
if ($item['tag'] === 'h3') {
if (!$open_sub) {
$toc_body .= '<ol class="toc-list toc-list--sub">';
$open_sub = true;
}
$toc_body .= '<li><a href="#' . $item['id'] . '">' . esc_html($item['title']) . '</a></li>';
} else {
if ($open_sub) {
$toc_body .= '</ol></li>';
$open_sub = false;
} else if (!empty($toc_body)) {
$toc_body .= '</li>';
}
$toc_body .= '<li><a href="#' . $item['id'] . '">' . esc_html($item['title']) . '</a>';
}
}
$toc_body .= $open_sub ? '</ol></li>' : '</li>';
$toc = '<h2>目次</h2><ol>' . $toc_body . '</ol>';
$content = $toc . $content;
}
}
return $content;
}
add_filter('the_content', 'my_toc');
H2のみを対象に目次を生成
function my_toc($content) {
if (is_single()) {
$pattern = '/<h2([^>]*)>(.*?)<\/h2>/is';
preg_match_all($pattern, $content, $matches, PREG_SET_ORDER);
if (count($matches) > 1) {
$toc = '<h2>目次</h2><ol>';
$i = 0;
foreach ($matches as $element) {
$i++;
$id = 'chapter-' . $i;
$original_heading = $element[0];
$new_heading = preg_replace(
'/<h2([^>]*)>/i',
'<h2$1 id="' . $id . '">',
$original_heading,
1
);
$content = preg_replace(
'/' . preg_quote($original_heading, '/') . '/',
$new_heading,
$content,
1
);
$title = trim(wp_strip_all_tags($element[2]));
$toc .= '<li><a href="#' . $id . '">' . esc_html($title) . '</a></li>';
}
$toc .= '</ol>';
$content = $toc . $content;
}
}
return $content;
}
add_filter('the_content', 'my_toc');あとがき
プラグインを使った方が簡単ではありますが、もっと細かくコードを変更したいとか、柔軟に対応したい時は上述した方法を試してみてください。




コメントを残す