WordPressテーマ「Cocoon」には標準で目次機能が搭載されています。プラグインなしで使える非常に便利な機能なのですが、実はこの目次、デフォルトでは「最初のH2タグの直前」に表示される仕様になっています。
私もこの仕様に悩まされた一人です。記事の書き出しを<p>タグで始めることが多く、導入文を書いてからH2見出しに入るスタイルで記事を書いています。すると目次が本文の途中に表示されてしまい、読者にとって見づらい構成になってしまっていました。
「記事の冒頭に導入文を書いてから見出しに入りたいけど、目次はもっと上に表示したい」
そんな悩みを抱えている方も多いのではないでしょうか?
今回は、Cocoonの目次をH2タグの位置に関係なく、本文の先頭(記事冒頭)に表示する方法を、私の実体験をもとに詳しく解説します。子テーマのfunctions.phpに追記するだけで実現できるので、ぜひ参考にしてください。
この記事でわかること
- Cocoonの目次機能の仕組みと表示位置の決まり方
- なぜ目次がH2タグの直前に表示されるのか
- 子テーマを使って目次位置をカスタマイズする具体的な方法
- コピペで使える実装コードと詳細な解説
- カスタマイズ後の動作確認とトラブルシューティング
Cocoonの目次機能について
標準機能として搭載されている目次
Cocoonには、プラグインなしで使える目次機能が標準搭載されています。WordPressの管理画面から「Cocoon設定」→「目次」タブを開くと、以下のような設定項目が用意されています。
- 目次の表示/非表示の切り替え
- 目次のタイトル(「目次」「この記事の内容」など)
- 表示する見出しの深さ(H2〜H6のどこまで含めるか)
- 目次を表示する見出し数の条件(何個以上の見出しがあれば表示するか)
- 開閉ボタンの有無
多くのユーザーにとって、この標準機能で十分なケースがほとんどです。しかし、「目次の表示位置」については設定項目が用意されていません。これが今回のカスタマイズが必要になる理由です。

デフォルトの表示位置は「最初のH2タグの前」

Cocoonでは、目次は「本文中の最初のH2タグの直前」に自動挿入されます。
例えば、以下のような記事構成の場合を考えてみましょう。
[アイキャッチ画像]
この記事では〇〇について解説します。
皆さんも〇〇で困った経験はありませんか?
(導入文が続く...)
## 最初の見出し ← ここの直前に目次が表示される
本文が続く...
## 次の見出し
本文が続く...
このように、導入文がどれだけ長くても、目次は最初のH2見出しの直前に表示されます。
この仕様が問題になるケース
多くの場合、このデフォルトの動作で問題ありません。しかし、以下のようなケースでは不都合が生じることがあります。
1. 導入文が長い場合
記事の冒頭で読者の悩みに共感したり、記事を読むメリットを伝えたりする導入文を書くスタイルの場合、目次にたどり着く前に読者が離脱してしまう可能性があります。また、記事の全体像を把握してから読み進めたい読者のニーズに応えられません。
2. H2を使わない記事構成の場合
H3から見出しを始める記事では、設定によっては目次が表示されないことがあります。また、特殊なレイアウトの記事で意図しない位置に表示されてしまうケースもあります。
3. デザイン上の理由
記事冒頭に目次を配置したいデザインコンセプトの場合や、アイキャッチ画像の直下に目次を表示したい場合には、現状の仕様では対応できません。
私の場合は、導入文を<p>タグで書き始める癖があり、目次が記事の途中に表示されてしまうことに長らく悩んでいました。
目次の表示位置を決めている仕組みを調査
カスタマイズを行う前に、Cocoonがどのように目次の位置を決定しているのか、ソースコードを調査しました。この調査結果を理解しておくと、なぜ今回のカスタマイズが有効なのかがよくわかります。
目次関連のファイル構成
Cocoonの目次機能は、主に以下のファイルで実装されています。
cocoon-master/
├── lib/
│ ├── toc.php ← メインの目次機能
│ ├── page-settings/
│ │ ├── toc-forms.php ← 目次設定フォーム
│ │ └── toc-funcs.php ← 目次設定関数
│ └── widgets/
│ └── toc.php ← 目次ウィジェット
この中で核となるのは lib/toc.php ファイルです。このファイルに目次の生成と挿入に関する処理がまとめられています。
目次挿入の処理を特定
lib/toc.php の311〜315行目付近を見ると、以下のようなフィルターフックが設定されています。
//最初のH2タグの前に目次を挿入する
//ref:https://qiita.com/wkwkrnht/items/c2ee485ff1bbd81325f9
add_filter('the_content', 'add_toc_before_1st_h2', get_toc_filter_priority());
add_filter('the_category_content', 'add_toc_before_1st_h2', get_toc_filter_priority());
add_filter('the_tag_content', 'add_toc_before_1st_h2', get_toc_filter_priority());
add_toc_before_1st_h2 という関数が、the_content フィルターを通じて本文に目次を挿入していることがわかります。関数名からも「最初のH2タグの前に目次を追加する」という意図が読み取れます。
実際の挿入処理の核心部分
同ファイルの389〜393行目付近に、目次を実際に挿入する核心部分があります。
if (is_total_the_page_toc_visible()) {
$h2result = get_h2_included_in_body( $the_content );//本文にH2タグが含まれていれば取得
$html = str_replace('<div class="toc ', '<div id="toc" class="toc ', $html);
$the_content = preg_replace(H2_REG, $html.PHP_EOL.PHP_EOL.$h2result, $the_content, 1);
}
ここで最も重要なのは最後の行です。
$the_content = preg_replace(H2_REG, $html.PHP_EOL.PHP_EOL.$h2result, $the_content, 1);
この処理を分解して解説すると、以下のようになります。
H2_REG:H2タグを検出する正規表現パターン$html:生成された目次のHTML$h2result:マッチしたH2タグ(<h2の部分)preg_replace(..., 1):最初の1つだけを置換
つまり、「最初のH2タグを見つけて、その前に目次HTMLを挿入する」という処理が行われています。これがデフォルトの動作の正体です。
H2_REG定数の定義
参考までに、H2タグを検出するための正規表現は lib/ad.php の166〜167行目で定義されています。
if ( !defined('H2_REG') )
define('H2_REG', apply_filters('insertion_heading_regexp', '/<h2/i'));//H2見出しのパターン
非常にシンプルで、<h2 という文字列にマッチする正規表現です。大文字小文字を区別しない(/iフラグ)設定になっています。
解決方法:子テーマでの関数オーバーライド
ここからは、実際に目次の表示位置を変更する方法を解説します。
なぜ子テーマを使うのか?
親テーマのファイルを直接編集することも技術的には可能ですが、テーマのアップデート時に変更が上書きされてしまいます。Cocoonは頻繁にアップデートが行われる活発なテーマなので、親テーマの直接編集は避けるべきです。
子テーマを使えば、親テーマの更新を受けつつ、自分のカスタマイズを維持できます。Cocoonには公式の子テーマ「Cocoon Child」が用意されているので、これを活用しましょう。

function_exists による上書き可能な設計
Cocoonのコードを詳しく見ると、多くの関数が以下のような形式で定義されていることがわかります。
if ( !function_exists( 'add_toc_before_1st_h2' ) ):
function add_toc_before_1st_h2($the_content){
// 処理内容
}
endif;
function_exists でチェックしているため、子テーマで同名の関数を先に定義すれば、親テーマの関数は読み込まれません。
これはWordPressの読み込み順序を活用した設計です。WordPressは子テーマの functions.php を親テーマより先に読み込むため、この仕組みが成立します。Cocoon開発者のわいひらさんが、カスタマイズしやすいように配慮してくれている設計です。
実装コード(コピペOK)
子テーマの functions.php に以下のコードを追加してください。
/**
* 目次を本文の先頭に表示する(最初のH2タグの前ではなく)
* 親テーマのadd_toc_before_1st_h2関数を上書き
*/
function add_toc_before_1st_h2($the_content){
global $_TOC_WIDGET_OR_SHORTCODE_USED;
//Table of Contents Plusプラグインが有効な際は目次機能は無効
if (class_exists( 'toc' )) {
return $the_content;
}
//プラグインのフォーラムページの場合は目次機能は無効
if (is_plugin_fourm_page()) {
return $the_content;
}
//ページ上で目次が非表示設定(ショートコードも未使用)になっている場合
if (!is_total_the_page_toc_visible() && !$_TOC_WIDGET_OR_SHORTCODE_USED && !is_active_widget( false, false, 'toc', true )) {
return $the_content;
}
$harray = array();
$depth = intval(get_toc_depth()); //2-6 0で全て
$set_depth = $depth;//
if (intval($set_depth) == 0) {
$set_depth = 6;
}
$html = get_toc_tag($the_content, $harray);
//目次タグが出力されない(目次が不要)時は、そのまま本文を返す
if (!$html) {
return $the_content;
}
///////////////////////////////////////
// PHPの見出し処理(条件によっては失敗するかも)
///////////////////////////////////////
$res = preg_match_all('/(<('.implode('|', $harray).')[^>]*?>)(.*?)(<\/h[2-6]>)/is', $the_content, $m);
$tag_all_index = 0;
$tag_index = 1;
$h_index = 2;
$h_content_index = 3;
$tag_end_index = 4;
if ($res && $m[0]) {
$i = 0;
$count = 1;
foreach ($m[$tag_all_index] as $value) {
$tag_all = $m[$tag_all_index][$i];
$tag = $m[$tag_index][$i];
$h = $m[$h_index][$i];
$h_content = get_h_inner_content($m[$h_content_index][$i]);
$tag_end = $m[$tag_end_index][$i];
$now_depth = intval(str_replace('h', '', $h));
//設定より見出しが深い場合はスキップ
if ($set_depth < $now_depth) {
$i++;
continue;
}
$new = $tag.'<span id="toc'.strval($count).'">'.$h_content.'</span>'.$tag_end;
$the_content = preg_replace('/'.preg_quote($value, '/').'/', $new, $the_content, 1);
$i++;
$count++;
}
}
//機能が有効な時のみ(ショートコードでは実行しない)
//is_singular()を追加してカテゴリー・タグページでは目次を表示しない
if (is_total_the_page_toc_visible() && is_singular()) {
$html = str_replace('<div class="toc ', '<div id="toc" class="toc ', $html);
// 目次を本文の先頭に挿入(H2の位置に関係なく)
$the_content = $html . PHP_EOL . PHP_EOL . $the_content;
}
return $the_content;
}
変更点の解説
親テーマのコードとの違いは、主に2点です。
変更点1:目次の挿入位置
親テーマ(変更前)
$the_content = preg_replace(H2_REG, $html.PHP_EOL.PHP_EOL.$h2result, $the_content, 1);
子テーマ(変更後)
$the_content = $html . PHP_EOL . PHP_EOL . $the_content;
親テーマでは preg_replace を使って最初のH2タグを検出し、その前に目次を挿入していました。子テーマでは、単純に本文の先頭に目次HTMLを連結しています。これにより、H2タグの位置に関係なく、常に記事の冒頭に目次が表示されるようになります。
また、親テーマでは $h2result(マッチしたH2タグ)を挿入していましたが、子テーマではこれが不要になるため、関連する get_h2_included_in_body() 関数の呼び出しも省略しています。
変更点2:カテゴリー・タグページでの非表示
親テーマ(変更前)
if (is_total_the_page_toc_visible()) {
子テーマ(変更後)
if (is_total_the_page_toc_visible() && is_singular()) {
is_singular() の条件を追加することで、投稿・固定ページのみに目次を表示し、カテゴリーページやタグページには表示されないようにしています。これにより、アーカイブページに意図せず目次が表示される問題を防ぎます。
なぜ他の処理はそのまま残すのか?
「目次の挿入位置を変更するだけなら、変更する部分だけ書けばいいのでは?」と思われるかもしれません。しかし、この関数は目次の挿入以外にも重要な処理を行っています。
1. プラグインとの競合チェック
Table of Contents Plusなど、他の目次プラグインが有効な場合は処理をスキップして競合を防ぎます。
2. 表示条件のチェック
投稿タイプごとの表示設定、個別記事での非表示設定、ショートコード使用時の挙動など、様々な条件をチェックしています。
3. 見出しへのID付与
目次からのページ内リンク用に、各見出しに id="toc1"、id="toc2" などを自動付与しています。この処理がないと、目次のリンクをクリックしても該当箇所にジャンプできません。
これらの処理を維持するため、関数全体をコピーして必要な部分だけ変更しています。
カスタマイズ後の動作確認
コードを追加したら、正しく動作しているか確認しましょう。
確認すべきポイント
以下の項目をチェックしてください。
1. 目次が記事冒頭に表示されるか
導入文よりも前(記事本文の一番上)に目次が表示されることを確認します。H2タグで始まる記事でも、<p>タグで始まる記事でも、同じ位置に表示されるはずです。
※ Before

※ After

2. 目次のリンクが正常に動作するか
目次の各項目をクリックして、該当の見出しにスムーズにジャンプするか確認します。ページ内リンクが正しく機能していれば成功です。
3. Cocoon設定の目次オプションが反映されるか
Cocoon設定の目次タブで設定した以下の項目が正しく反映されるか確認します。
- 目次タイトルの変更
- 表示する見出しの深さ
- 開閉ボタンの動作
- 表示条件(見出しの数)
4. 個別記事での非表示設定が効くか
投稿編集画面の「目次を表示しない」チェックボックスが正しく機能するか、特定の記事で目次を非表示にできるか確認します。
5. カテゴリー・タグページで目次が表示されないか
カテゴリーページやタグページを表示して、目次が表示されていないことを確認します。is_singular() の条件により、これらのページでは目次が非表示になります。
トラブルシューティング
うまく動作しない場合は、以下の点を確認してください。
目次が表示されない場合
- ブラウザのキャッシュをクリアする
- キャッシュプラグインを使用している場合はキャッシュを削除する
- Cocoon設定で目次が有効になっているか確認する
- 記事内の見出し数が、表示条件の設定値を満たしているか確認する
PHPエラーが発生する場合
- コードのコピー時に全角スペースが混入していないか確認する
- 開始タグ
<?phpの重複がないか確認する(functions.phpの先頭に既にある場合は追記しない) - 閉じタグや波括弧の対応が正しいか確認する
目次が2つ表示される場合
子テーマの関数が親テーマより先に読み込まれていない可能性があります。子テーマが正しく有効化されているか確認してください。
カテゴリーページにも目次が表示される場合
コードに is_singular() の条件が正しく追加されているか確認してください。この条件がないと、カテゴリーページやタグページにも目次が表示されてしまいます。
別のアプローチ:ショートコードを使う方法
参考までに、今回のカスタマイズ以外にも目次の位置を変更する方法があります。
Cocoonには [ toc ] ショートコードが用意されています。Cocoon設定で目次の自動表示を無効にして、各記事で [ toc ] ショートコードを好きな位置に挿入する方法もあります。
ショートコード方式のメリット
- 記事ごとに目次の位置を自由に変えられる
- コードを書く必要がない
- 特定の記事だけ目次位置を変更したい場合に便利
ショートコード方式のデメリット
- 毎回手動で挿入する必要がある
- ショートコードを入れ忘れるリスクがある
- 既存の全記事を一括で変更できない
- 運用が煩雑になりがち
「全ての記事で統一して冒頭に表示したい」という場合は、今回紹介した子テーマでのカスタマイズがおすすめです。一度設定すれば、新規記事も既存記事も自動的に適用されます。
まとめ
今回は、Cocoonの目次を本文の先頭に表示するカスタマイズ方法を解説しました。
ポイントのおさらい
- Cocoonの目次はデフォルトで最初のH2タグの前に表示される
- 表示位置は
lib/toc.phpのadd_toc_before_1st_h2関数で制御されている - 子テーマで同名関数を定義することでオーバーライド可能
- 主な変更は2点:目次挿入位置の変更と、
is_singular()によるページ種別の制限 - 他の処理(見出しへのID付与など)は維持する必要がある
注意点
親テーマのアップデートで add_toc_before_1st_h2 関数に大きな変更があった場合、子テーマのコードも更新が必要になる可能性があります。Cocoonのアップデート時には、変更内容をチェックすることをおすすめします。
とはいえ、この関数の基本的なロジックが大きく変わることは稀です。万が一変更があっても、今回の記事で解説した考え方を理解していれば、対応は難しくないでしょう。
Cocoonは非常にカスタマイズしやすい設計になっています。今回のように function_exists でラップされている関数は、子テーマから安全にオーバーライドできます。目次の位置を変えたいと考えている方は、ぜひ試してみてください。

コメント