この記事ではWordPress自作テーマで目次を作る方法を解説しています。
サンプルコードを紹介していますが、WordPressの投稿に目次を自動生成する機能です。見出しタグ(h2~h6)から目次を自動生成するようになっています。
このコードによって生成される目次は見出しに番号が振られ、見出しの階層(ネスト)が反映されたデザインになります。見出しタグ(h2〜h6)が存在する場合、それぞれに対応した番号が自動的に振られ、見出しが階層化されます。
目次の階層化と番号の振り方
階層化: 例えば、h2タグの中にh3タグがある場合、h2が親要素として扱われ、h3はその中にネストされた要素(子要素)として扱われます。このように、h2からh6までの見出しタグが階層に応じて入れ子(ネスト)状に表示されます。
番号の振り方: 各階層内で番号が振られるようになっており、h2タグは「1.」「2.」のように、h3タグはその親要素であるh2の中で「1.」「2.」のように階層ごとに番号が付きます。
サンプルコード
このコードは、WordPressの投稿に対して目次(Table of Contents)を自動的に生成し、本文の中に挿入する機能を提供します。以下に、各部分を詳しく説明します。
generate_table_of_contents 関数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
function generate_table_of_contents($headings) { $toc = '<div class="toc__container"><p class="toc__title">目次</p><ol class="toc__list">'; $current_level = 1; // Tracks the current heading level $numbering = [0, 0, 0, 0, 0, 0]; // Array to track numbering for h2 to h6 foreach ($headings as $heading) { $heading_level = intval(substr($heading['tag'], 1)); // Get level from tag (h2 => 2) // Adjust numbering and open/close lists based on the level if ($heading_level > $current_level) { for ($i = $current_level; $i < $heading_level; $i++) { $numbering[$i] = 0; // Reset numbering for new level $toc .= '<ol class="toc__sublist">'; // Open new sublist } } elseif ($heading_level < $current_level) { while ($current_level > $heading_level) { $toc .= '</ol>'; // Close the sublist $current_level--; } } // Update numbering for the current level $numbering[$heading_level - 1]++; // Generate the numbering string $number_string = implode('.', array_slice($numbering, 0, $heading_level)); // Keep only the first segment of the numbering string $number_string = explode('.', $number_string)[0]; // Add the heading item with numbering, ensuring that leading '0' is removed $number_string = ltrim($number_string, '0'); $number_string = ($number_string === '') ? '' : $number_string; $toc .= '<li class="toc__item toc__item--h' . $heading_level . '"><a href="#' . $heading['id'] . '" class="toc__link">' . $number_string . ' ' . $heading['text'] . '</a></li>'; // Set the current level to the heading level $current_level = $heading_level; } // Close any remaining open lists while ($current_level > 1) { $toc .= '</ol>'; $current_level--; } $toc .= '</ol></div>'; return $toc; } |
役割
見出しタグ(h2~h6)から目次を生成し、HTML形式で返す関数です。
引数
$headings:投稿本文から抽出された見出し情報(タグ、テキスト、ID)を含む配列。
処理の流れ
$toc:目次のHTMLを格納する変数で、目次のラップ用の <div> 要素と <ol>(番号付きリスト)で初期化されています。
$current_level:現在処理している見出しの階層(h2~h6)。初期値は1(h2)。
$numbering:各階層の見出しの番号を追跡する配列です。見出しレベルごとに番号が増えるため、各レベルに対して番号を保持します。
見出しの処理
ループで $headings の各見出しを処理します。
$heading_level:見出しタグ(例: h2, h3)のレベル(2, 3など)を取得し、現在の階層と比較します。
階層の変化に応じたリストの制御
見出しのレベルが現在のレベルより高ければ、ネストされたリストを作成するために <ol> を追加。
見出しのレベルが低ければ、現在のレベルに戻るまでリストを閉じるために </ol> を追加します。
番号付け
各レベルの番号をインクリメントし、全階層の番号を結合して番号を作成します。
番号のフォーマットを調整し、先頭の「0」を削除して、最上位のレベルの番号だけを表示します。
見出しのリスト化
各見出しは <li> 要素として目次に追加され、<a> タグでその見出しにリンクされます。
最後の処理
すべての見出しを処理した後、まだ閉じていないリストがあれば、</ol> タグで閉じます。
完成した目次HTMLを返します。
insert_table_of_contents 関数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
function insert_table_of_contents($content) { if (is_single()) { $heading_pattern = '/<(h[2-6])(.*?)>(.*?)<\/\1>/is'; preg_match_all($heading_pattern, $content, $matches, PREG_SET_ORDER); if (count($matches) >= 2) { $headings = array(); $heading_counter = 1; foreach ($matches as $match) { $headings[] = array( 'tag' => $match[1], 'attributes' => $match[2], 'text' => $match[3], 'id' => 'heading-' . $heading_counter ); $heading_counter++; } $content = preg_replace_callback($heading_pattern, function ($match) use ($headings) { static $index = 0; $id = $headings[$index]['id']; $index++; return sprintf('<%1$s%2$s><span id="%3$s">%4$s</span></%1$s>', $match[1], $match[2], $id, $match[3]); }, $content); $table_of_contents = generate_table_of_contents($headings); $first_h2_position = strpos($content, '<h2>'); if ($first_h2_position !== false) { $content = substr_replace($content, $table_of_contents, $first_h2_position, 0); } } } return $content; } |
役割
投稿の本文に対して目次を挿入する関数です。
処理の流れ
見出しの検索:正規表現で本文中の h2~h6 タグをすべて検索します。$heading_pattern 変数に正規表現が定義されています。
<(h[2-6])(.*?)>(.*?)<\/\1> のパターンで、h2~h6タグの開始タグ・終了タグ、その中のテキストをマッチさせます。
preg_match_all() 関数を使って、全てのマッチ結果を $matches 配列に格納します。
見出しの配列作成
見つかった見出しを $headings 配列に格納します。この配列には、タグ名、属性、テキスト、IDが含まれます。
各見出しにはユニークなIDが付与され、heading-1, heading-2 のように番号が振られます。
本文内の見出しにIDを追加
preg_replace_callback() を使用して、見出しにIDを追加しながら本文を置換します。各見出しは <span> タグで囲まれ、IDが挿入されます。
目次の生成と挿入
generate_table_of_contents() 関数を呼び出し、目次を生成します。
最初の h2 タグの前に目次を挿入するために、strpos() 関数を使用して本文中の最初の h2 タグを見つけ、その位置に目次HTMLを挿入します。
add_filter フック
1 |
add_filter('the_content', 'insert_table_of_contents'); |
WordPressの the_content フィルターフックを利用して、投稿の本文に目次を自動的に挿入します。
add_filter(‘the_content’, ‘insert_table_of_contents’); で、投稿の本文が表示される際に、insert_table_of_contents() 関数が実行されます。
コード全文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
<?php //目次 function generate_table_of_contents($headings) { $toc = '<div class="toc__container"><p class="toc__title">目次</p><ol class="toc__list">'; $current_level = 1; // Tracks the current heading level $numbering = [0, 0, 0, 0, 0, 0]; // Array to track numbering for h2 to h6 foreach ($headings as $heading) { $heading_level = intval(substr($heading['tag'], 1)); // Get level from tag (h2 => 2) // Adjust numbering and open/close lists based on the level if ($heading_level > $current_level) { for ($i = $current_level; $i < $heading_level; $i++) { $numbering[$i] = 0; // Reset numbering for new level $toc .= '<ol class="toc__sublist">'; // Open new sublist } } elseif ($heading_level < $current_level) { while ($current_level > $heading_level) { $toc .= '</ol>'; // Close the sublist $current_level--; } } // Update numbering for the current level $numbering[$heading_level - 1]++; // Generate the numbering string $number_string = implode('.', array_slice($numbering, 0, $heading_level)); // Keep only the first segment of the numbering string $number_string = explode('.', $number_string)[0]; // Add the heading item with numbering, ensuring that leading '0' is removed $number_string = ltrim($number_string, '0'); $number_string = ($number_string === '') ? '' : $number_string; $toc .= '<li class="toc__item toc__item--h' . $heading_level . '"><a href="#' . $heading['id'] . '" class="toc__link">' . $number_string . ' ' . $heading['text'] . '</a></li>'; // Set the current level to the heading level $current_level = $heading_level; } // Close any remaining open lists while ($current_level > 1) { $toc .= '</ol>'; $current_level--; } $toc .= '</ol></div>'; return $toc; } function insert_table_of_contents($content) { if (is_single()) { $heading_pattern = '/<(h[2-6])(.*?)>(.*?)<\/\1>/is'; preg_match_all($heading_pattern, $content, $matches, PREG_SET_ORDER); if (count($matches) >= 2) { $headings = array(); $heading_counter = 1; foreach ($matches as $match) { $headings[] = array( 'tag' => $match[1], 'attributes' => $match[2], 'text' => $match[3], 'id' => 'heading-' . $heading_counter ); $heading_counter++; } $content = preg_replace_callback($heading_pattern, function ($match) use ($headings) { static $index = 0; $id = $headings[$index]['id']; $index++; return sprintf('<%1$s%2$s><span id="%3$s">%4$s</span></%1$s>', $match[1], $match[2], $id, $match[3]); }, $content); $table_of_contents = generate_table_of_contents($headings); $first_h2_position = strpos($content, '<h2>'); if ($first_h2_position !== false) { $content = substr_replace($content, $table_of_contents, $first_h2_position, 0); } } } return $content; } add_filter('the_content', 'insert_table_of_contents'); |
CSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
/* 目次 */ .toc__list, .toc__sublist { padding-left: 1em; margin-left: 0; } .toc__item { margin: 0; } .toc__link { text-decoration: none; color: inherit; /* リンクの色を親要素の色に合わせる */ } .toc__title { text-align: center; } .toc__container { border: 1px solid #ccc; border-radius: 4px; padding: 10px; margin: 20px auto; max-width: 600px; padding-left: 30px; background-color: #fdfdfd; } @media screen and (max-width: 767px) { .toc__container { padding-left: 0; } } |
目次をCSSでデザイン調整しています。
目次全体(.toc__container): 枠線、背景色、角を丸くして、中央に配置。最大幅600pxに制限し、余白とパディングを追加。
タイトル(.toc__title): 文字を中央に揃え。
リスト(.toc__list, .toc__sublist): 左に余白を付け、階層化された見出しをインデント。
リスト項目(.toc__item): アイテム同士の余白をなくす。
リンク(.toc__link): 下線を消し、色は親要素に従わせる。
レスポンシブ対応: 画面幅767px以下では、パディングを左に0にしてモバイル表示に最適化。
好みでデザイン調整してください。