WordPressで投稿ページにプラグインなしで目次を作る方法

⭐️ WordPressでページ内に目次を作りたい!と思う場合があると思います。
目次を作る方法は3つあります。

  • 自分で毎回手入力して作る。
  • プラグインを使う。
  • テーマの「functions.php」と「style.css」を編集して作る。

毎回、手打ちで目次を作成するのは面倒!
プラグインを使うとサイトの動作が重くなるので 「functions.php」と「style.css」を編集して目次を自動で出力してくるショートコードを作るというのが現状ベストかなと思っています。
今回は、プラグインを使わずPHPでページ内の目次を自動で作成し、出力します。

ワードプレスで効率良く目次を作りたい!目次の作り方

目次とは?

目次と言うのは冒頭にある部分です。
記事の概要が一目で分かるし、見出しをクリックすればそこにジャンプできできて便利です。

「プラグイン」を使えば良くない?

プラグインは基本的にワードプレスの動作を重くします。互換性やセキュリティなど管理することも増えるので..)

毎回手打ちで目次を作成するのは効率よくないので、ここでは「プラグインなし」でテーマの「functions.php」を編集し、目次を自動出力できる方法をご紹介します。

作りたい目次

👉🏻 必要な時だけショートコードで目次を呼び出す。
 全ての記事に自動で目次が出てくる必要ないと思うのでショートコードで必要な時だけ呼び出せるコードを使用します。

目次を自動生成するショートコードの作り方

テーマの「functions.php」「style.css」を編集します。
テーマを編集するので、バックアップをしたり、子テーマでの編集するのをおすすめします。

以下のコードは [ Xakuro Blog ] さんの記事を参考して、編集したものです。

「functions.php」の編集

下記のコードをコピーして「functions.php」に記述します。

class Toc_Shortcode {

	private $add_script = false;
	private $atts = array();

	public function __construct() {
		add_shortcode( 'toc', 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',
		), $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();
		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();

「style.css」の編集

下記のコードをコピーして「style.css」に記述します。

 .toc {
    position: relative;
    margin-top: 5%;
    background: #ececec;
    padding: 1% 2%;
    word-break: break-all;
    word-wrap: break-word;
    border: 1px solid #ececec;
    border-radius: 8px;
}
.toc-title {
    text-align: center;
    font-size: 18px;
    letter-spacing: 3px;
    margin: 0.5%;
    font-weight: bold;
}
.toc-toggle {
    position: absolute;
    right: 12px;
}
.toc-list {
    padding: 9px;
    list-style: none;
    line-height: 1.7;
}
.toc-list ul {
    list-style: square;
}

ショートコード

目次を入れたいページに、

[toc]

と入力すれば、目次が自動出力されます。

使用例:

[toc title="目次" depth="2"]

参考:Xakuro Blog