Wordpressで「特定の見出し」の目次を作成する方法を備忘録を兼ねてシェアします。
目次というのは基本的に、記事ページ内に含まれる全てのHタグを抽出しますが、特定のHタグのみ抽出して目次を作成することができます。
使いどころとしては、あるH2タグの傘下の中だけで小目次を作りたい場合や、誘導用のピックアップ目次を作りたい場合に有効かと思います。
なお、以下で紹介するコードは、Xakuro Blogさんで公開されているものをベースに一部改変したものとなります。こちらのオリジナルコードの強みは、jQueryを一切使っていないためWordpressで設置しやすいところでしょうね。
サンプルコード
ではまず、サンプルコードから。
こちらのコードをfunction.phpに貼り付けます。オリジナルのコードと異なる箇所を赤字にしています。説明はのちほど。
- class Toc_Shortcode {
- private $add_script = false;
- private $atts = array();
- public function __construct() {
- add_shortcode( 'itoc', array( $this, 'shortcode_content' ) );
- add_action( 'wp_footer', array( $this, 'add_script' ), 999999 );
- add_filter( 'the_content', array( $this, 'change_content' ), 9 );
- }
- function change_content( $content ) {
- return "<div id=\"toc_content\">{$content}</div>";
- }
- public function shortcode_content( $atts ) {
- global $post;
- if ( ! isset( $post ) )
- return '';
- $this->atts = shortcode_atts( array(
- 'id' => '',
- 'class' => 'toc',
- 'title' => '目次',
- 'toggle' => true,
- 'opentext' => '開く',
- 'closetext' => '閉じる',
- 'close' => false,
- 'showcount' => 2,
- 'depth' => 0,
- 'toplevel' => 2,
- 'scroll' => 'smooth',
- 'mode' => '0',
- ), $atts );
- $this->atts['toggle'] = ( false !== $this->atts['toggle'] && 'false' !== $this->atts['toggle'] ) ? true : false;
- $this->atts['close'] = ( false !== $this->atts['close'] && 'false' !== $this->atts['close'] ) ? true : false;
- $content = $post->post_content;
- $headers = array();
- if($this->atts['mode'] == '1'){
- preg_match_all( '/<([hH][1-6]).*?itoc1.*?>(.*?)<\/[hH][1-6].*?>/u', $content, $headers );
- }
- elseif($this->atts['mode'] == '2'){
- preg_match_all( '/<([hH][1-6]).*?itoc2.*?>(.*?)<\/[hH][1-6].*?>/u', $content, $headers );
- }
- elseif($this->atts['mode'] == '3'){
- preg_match_all( '/<([hH][1-6]).*?itoc3.*?>(.*?)<\/[hH][1-6].*?>/u', $content, $headers );
- }
- else {
- preg_match_all( '/<([hH][1-6]).*?>(.*?)<\/[hH][1-6].*?>/u', $content, $headers );
- }
- $header_count = count( $headers[0] );
- $counter = 0;
- $counters = array( 0, 0, 0, 0, 0, 0 );
- $current_depth = 0;
- $prev_depth = 0;
- $top_level = intval( $this->atts['toplevel'] );
- if ( $top_level < 1 ) $top_level = 1;
- if ( $top_level > 6 ) $top_level = 6;
- $this->atts['toplevel'] = $top_level;
- // 表示する階層数
- $max_depth = ( ( $this->atts['depth'] == 0 ) ? 6 : intval( $this->atts['depth'] ) );
- $toc_list = '';
- for ( $i = 0; $i < $header_count; $i++ ) {
- $depth = 0;
- switch ( strtolower( $headers[1][$i] ) ) {
- case 'h1': $depth = 1 - $top_level + 1; break;
- case 'h2': $depth = 2 - $top_level + 1; break;
- case 'h3': $depth = 3 - $top_level + 1; break;
- case 'h4': $depth = 4 - $top_level + 1; break;
- case 'h5': $depth = 5 - $top_level + 1; break;
- case 'h6': $depth = 6 - $top_level + 1; break;
- }
- if ( $depth >= 1 && $depth <= $max_depth ) {
- if ( $current_depth == $depth ) {
- $toc_list .= '</li>';
- }
- while ( $current_depth > $depth ) {
- $toc_list .= '</li></ul>';
- $current_depth--;
- $counters[$current_depth] = 0;
- }
- if ( $current_depth != $prev_depth ) {
- $toc_list .= '</li>';
- }
- if ( $current_depth < $depth ) {
- $class = $current_depth == 0 ? ' class="toc-list"' : '';
- $style = $current_depth == 0 && $this->atts['close'] ? ' style="display: none;"' : '';
- $toc_list .= "<ul{$class}{$style}>";
- $current_depth++;
- }
- $counters[$current_depth - 1]++;
- $number = $counters[0];
- for ( $j = 1; $j < $current_depth; $j++ ) {
- $number .= '.' . $counters[$j];
- }
- $counter++;
- $toc_list .= '<li><a href="#toc' . ($i + 1) . '"><span class="contentstable-number">' . $number . '</span> ' . $headers[2][$i] . '</a>';
- $prev_depth = $depth;
- }
- }
- while ( $current_depth >= 1 ) {
- $toc_list .= '</li></ul>';
- $current_depth--;
- }
- $html = '';
- if ( $counter >= $this->atts['showcount'] ) {
- $this->add_script = true;
- $toggle = '';
- if ( $this->atts['toggle'] ) {
- $toggle = ' <span class="toc-toggle">[<a class="internal" href="javascript:void(0);">' . ( $this->atts['close'] ? $this->atts['opentext'] : $this->atts['closetext'] ) . '</a>]</span>';
- }
- $html .= '<div' . ( $this->atts['id'] != '' ? ' id="' . $this->atts['id'] . '"' : '' ) . ' class="' . $this->atts['class'] . '">';
- $html .= '<p class="toc-title">' . $this->atts['title'] . $toggle . '</p>';
- $html .= $toc_list;
- $html .= '</div>' . "\n";
- }
- return $html;
- }
- public function add_script() {
- if ( ! $this->add_script ) {
- return false;
- }
- $var = wp_json_encode( array(
- 'open_text' => isset( $this->atts['opentext'] ) ? $this->atts['opentext'] : '開く',
- 'close_text' => isset( $this->atts['closetext'] ) ? $this->atts['closetext'] : '閉じる',
- 'scroll' => isset( $this->atts['scroll'] ) ? $this->atts['scroll'] : 'smooth',
- ) );
- ?>
- <script type="text/javascript">
- var xo_toc = <?php echo $var; ?>;
- let xoToc = () => {
- const entryContent = document.getElementById('toc_content');
- if (!entryContent) {
- return false;
- }
- /**
- * スムーズスクロール関数
- */
- let smoothScroll = (target, offset) => {
- const targetRect = target.getBoundingClientRect();
- const targetY = targetRect.top + window.pageYOffset - offset;
- window.scrollTo({left: 0, top: targetY, behavior: xo_toc['scroll']});
- };
- /**
- * アンカータグにイベントを登録
- */
- const wpadminbar = document.getElementById('wpadminbar');
- const smoothOffset = (wpadminbar ? wpadminbar.clientHeight : 0) + 2;
- const links = document.querySelectorAll('.toc-list a[href*="#"]');
- for (let i = 0; i < links.length; i++) {
- links[i].addEventListener('click', function (e) {
- const href = e.currentTarget.getAttribute('href');
- const splitHref = href.split('#');
- const targetID = splitHref[1];
- const target = document.getElementById(targetID);
- if (target) {
- e.preventDefault();
- smoothScroll(target, smoothOffset);
- } else {
- return true;
- }
- return false;
- });
- }
- /**
- * ヘッダータグに ID を付与
- */
- const headers = entryContent.querySelectorAll('h1, h2, h3, h4, h5, h6');
- for (let i = 0; i < headers.length; i++) {
- headers[i].setAttribute('id', 'toc' + (i + 1));
- }
- /**
- * 目次項目の開閉
- */
- const tocs = document.getElementsByClassName('toc');
- for (let i = 0; i < tocs.length; i++) {
- const toggle = tocs[i].getElementsByClassName('toc-toggle')[0].getElementsByTagName('a')[0];
- toggle.addEventListener('click', function (e) {
- const target = e.currentTarget;
- const tocList = tocs[i].getElementsByClassName('toc-list')[0];
- if (tocList.hidden) {
- target.innerText = xo_toc['close_text'];
- } else {
- target.innerText = xo_toc['open_text'];
- }
- tocList.hidden = !tocList.hidden;
- });
- }
- };
- xoToc();
- </script>
- <?php
- }
- }
- new Toc_Shortcode();
以下、オリジナルコードからの変更箇所について説明します。
5行目:ショートコード名を変更
オリジナルではショートコード名が[toc]となっており、これは「table of contents(目次)」の略なので、いかにも他とバッティングしそうですよね。ここでは、ショートコード名を[itoc]に変更しています。
- add_shortcode( 'itoc', array( $this, 'shortcode_content' ) );
28行目:パラメータ「mode」を追加
ショートコードのパラメータを追加します。[itoc mode=1]のようにパラメータの数字を指定することで、目次に抽出する見出しの範囲を変更します。詳しくは後ほど。
- 'mode' => '0',
34行目以降:条件分岐を追加
オリジナルでは記事本文から全てのHタグを拾ってくるプログラムですが、特定のクラス(ここでは「itoc1~3」)が付いたHタグを拾えるようにしています。
さらに、前出の追加パラメータ「mode」の数字により条件分岐させ、mode=1ならitoc1の付いた見出しだけを、mode=2ならitoc2の付いた見出しだけを拾えます。3つで足りなければ、条件分岐を増やしてください。なお、パラメータを設定しなければ、オリジナルと同じ挙動(全てのHタグを抽出)となります。
- if($this->atts['mode'] == '1'){
- preg_match_all( '/<([hH][1-6]).*?itoc1.*?>(.*?)<\/[hH][1-6].*?>/u', $content, $headers );
- }
- elseif($this->atts['mode'] == '2'){
- preg_match_all( '/<([hH][1-6]).*?itoc2.*?>(.*?)<\/[hH][1-6].*?>/u', $content, $headers );
- }
- elseif($this->atts['mode'] == '3'){
- preg_match_all( '/<([hH][1-6]).*?itoc3.*?>(.*?)<\/[hH][1-6].*?>/u', $content, $headers );
- }
- else {
- preg_match_all( '/<([hH][1-6]).*?>(.*?)<\/[hH][1-6].*?>/u', $content, $headers );
- }
以上で作業終了です。CSSはXakuro Blogさんに掲載のものをお使いください。
試しに、Wordpressのエディタ画面に以下を入力すると、「表示します」だけの目次が作られると思います。
- [itoc mode=1]
- <h3 class="itoc1">表示します</h3>
- <h3 class="itoc1">表示します</h3>
- <h3 class="itoc1">表示します</h3>
- <h3 class="itoc2">表示しません!</h3>
手短ですが、以上となります。