ZATYのBLOG

お問い合わせする

WordPressの記事に目次を自動生成(プラグイン なし)してSEO対策を図る

zb003WordPressの記事に目次を自動生成してSEO対策を図るアイキャッチ画像

書籍に目次があるようにブログサイトにも目次を設置するのが当たり前になってきました。目次は単にユーザーが内容を把握できるだけでなく、Googleエンジンが目次を認識することでのSEO効果も存在します。

この記事では、目次によるSEO効果とWordPressで目次を自動生成するプログラムを紹介します。

目次によるSEO効果

SEO対策にはユーザビリティ的な観点とサイト構造的な観点で評価対象を分けることができます。Googleのクローラーが認識できる構造部分をSEO対策してもユーザビリティが向上するとは限らないですし、その逆も同じことが言えます。

そこで目次のSEO効果を考えてみます。

一目で内容を把握して離脱率を下げる

誰もが最初に思いつくのは、目次を見ることでそのページの内容を把握できることでしょう。

ブログは2000文字程度書くの良いと言われていますが、ユーザーは基本的に2000文字全てを読むことはありません。しかし、内容が少ないとGoogle的にはコンテンツが評価されません。目次の設置によって長い文章をユーザーが一目で把握でき、目的の情報まで辿りつきやすくする効果があります。

これによりユーザーは「ああ、文章が長い。。欲しい情報がどこに書かれているのかわからない。。」ということが減り、内容を読まずに直帰する確率を減らすことに役立つでしょう。

逆に内容の少ないページに関しては、目次で自分の求めている情報がないことがわかって直帰することが増えることもあるでしょう。

検索結果に目次の一部が表示される

Googleで検索した時、タイトルとサイト説明とは別に目次の一部がリンクとなって検索結果に表示されることがあります。これは、Googleのクローラーが設置した目次を目次と認識した際に表示してくれるものですが、上位のサイトほど表示してもらえることが多いようです。

検索結果に表示できる情報は少しでも多い方がサイトに訪問してもらえる(CTRの向上に繋がる)ことが増えるでしょう。

PHPで目次を生成する

目次の生成はサーバーサイドで生成する方法とクライアントサイド(JavaScript)で生成する方法の2種類があります。

クライアントサイドで生成してもGoogleのクローラーは正常に目次として認識してくれることがわかっていますが、JavaScriptではHTMLが生成されてからでないと動作させられない点で、生成速度の観点から今回はPHPで自動生成させます。

コード全文を紹介します。

<?php
/**
* generate table of contents
*/

/**
 * $content_htmlの中に含まれる$change_htmlのDOMに$idで指定したid属性を追加する
 * $change_htmlは開始タグから閉じタグまでの唯一の1文であると想定している。2文以上ではエラーは吐かずにバグる
 * @return DOMObject $content_html idを追加した$change_htmlを含んだcontent_htmlを返す
 */
function add_id_attribute_to_content ( $content_html, $change_html, $id )
{
  // $change_htmlが既にid属性を持っているか確認
  preg_match( "/id/i", $change_html, $if_included_id );
  $if_included_id = count($if_included_id) !== 0;

  if( $if_included_id ) //id属性を既に持っている
  {
    $modified_html = preg_replace( '/id=([\"\'])?([^\"\']+)/', 'id=$1$2 ' . $id, $change_html );
  }else
  {
    $modified_html = preg_replace( "/^(<h[\w\s]+)>/m", '$1 id="' . $id . '">', $change_html );
  }

  $content_html = str_replace( $change_html, $modified_html, $content_html );

  return $content_html;
}

function create_table_content ( $exploded_content, $index_prefix_name )
{
  $tmp_header_number = 0;
  $previos_header_number = 0;
  $id = 0;

  // 目次のリストの中身部分の作成
  $inner_table = "";
  foreach ( $exploded_content[0] as $one_line_header_html )
  {
    $tmp_header_number = (int) mb_substr( $one_line_header_html, 2, 1 );

    $current_header_number = $tmp_header_number;

    // 子リストの生成
    if( $current_header_number > $previos_header_number ) {

      $inner_table = $inner_table . '<ul class="tr-contents-table__lists tr-contents-table__lists--child">';

    }else{

      // 子リストの閉じタグ生成
      for(
        $r = $current_header_number;
        $r < $previos_header_number;
        $r++
      )
      {
        $inner_table = $inner_table . '</li></ul>';
      }

      if( $inner_table !== '' ){
        $inner_table = $inner_table . '</li>';
      }

    }
    $id_name = $index_prefix_name . $id;

    // 表のlistタグ生成
    $insert_text = preg_replace( '/<\/?[\w\s]+>/', '', $one_line_header_html );

    $inner_table = $inner_table .
    "<li class='tr-contents-table__item'>" .
      "<a class='tr-contents-table__link' href='#{$id_name}'>" .
        esc_html($insert_text) .
      "</a>";

    $previos_header_number = $current_header_number;
    $id++;
  }

  $table_html = '<ol class="tr-contents-table__lists">' . $inner_table . '</ol>';

  $table_wrapper = '<div class="tr-contents-table"><p class="tr-contents-table__title"><span class="tr-contents-table__button"></span>目次</p>' . $table_html . '</div>';

  return $table_wrapper;
}

function troms_table_content ( $content )
{
  if(!is_single()) return;
  // hタグの取得する正規表現。ここでは</h2>と</h3>を取得している
  $tag_patterm = "/^.+?<\/h[23]>$/im";

  // コンテンツ内のhタグに追加する目次用のid属性のプレフィックス
  $index_prefix_name = "troms_table_";

  preg_match_all( $tag_patterm, $content, $exploded_content );

  $table_html = create_table_content( $exploded_content, $index_prefix_name );

  $i=0;
  foreach ($exploded_content[0] as $one_line_header_html )
  {
    $id_name = $index_prefix_name . $i;

    // 本文に表と対応したid属性を追加
    $content = add_id_attribute_to_content( $content, $one_line_header_html, $id_name );
    $i++;
  }

  //コンテンツに目次を挿入
  preg_match( "/^.+?<\/h[23]>$/im", $content, $first_header_html, PREG_OFFSET_CAPTURE );

  $introduce_before_header = substr($content, 0, $first_header_html[0][1] );
  $content = $introduce_before_header . $table_html . substr( $content, $first_header_html[0][1]);

  return $content;
}
add_filter( 'the_content', 'troms_table_content', 9999 );

このコードをfunctions.phpに貼り付けるだけで自動的に投稿ページに目次が追加されます。

生成した目次をデザインする

上記のPHPによって必要な目次のHTMLは生成完了しています。デザインはサイトに合うようコーディングしてください。以下参考にこのブログ記事のSCSSコードを紹介します。

CSSでデザイン

デザインはSassのSCSS記法でCSSを記述していきます。

$mainColor: #333333;
body
{
  .tr-contents-table{
    width: 62.5%;
    padding: 2rem 3.6rem 2.4rem;
    border: .2rem solid $mainColor;
    border-radius: .8rem;
    box-shadow: .4rem .4rem .1rem rgba(#333, .25);
    margin: 4rem 0 6.4rem;
    overflow: hidden;
    transition: .5s max-height;
    &__title{
      font-size: 2rem;
      font-weight: 500;
      margin: 0;
      margin: 0 0 2.4rem;
      line-height: 1.6em;
      padding-left: .8rem;
      font-weight: 500;
      &:after{
        content: '';
        display: block;
        position: absolute;
        width: 64%;
        height: .3rem;
        background: #333;
        left: 6.4rem;
        top: 50%;
      }
    }
    &__lists{
      list-style-type: "・";
      padding: 0;
      padding-left: .4rem;
      margin: 0;
      &--child{
        margin: .8rem 0 0 1.2rem;
        .tr-contents-table__item{
          margin-top: .8rem;
          font-weight: 400;
        }
      }
    }
    &__item{
      font-size: 1.6rem;
      line-height: 2em;
      margin-top: 1.6rem;
      padding-left: .4rem;
      font-weight: 500;
      &:first-of-type{
        margin-top: 0;
      }
    }
    &__link{
      color: $mainColor;
      &:hover{
        color: #EBBC00;
      }
    }
    &__button{
      display: block;
      position: absolute;
      left: -2rem;
      top: 50%;
      transform: translateY(-50%);
      width: 0;
      height: 0;
      border: {
        top: 1.2rem solid transparent;
        left: 1.2rem solid $mainColor;
        bottom: 1.2rem solid transparent;
      }
      transform-origin: center;
      transition: .8s left, .4s transform;
      &--clicked{
        left: 100%;
        transform: translateY(-50%) rotate(180deg);
      }
    }
  }
}
@media(max-width: 768px){
  .content-main
  {
    .tr-contents-table{
      width: 95%;
      padding: 2rem 2.8rem 2.4rem;
      margin: auto;
      margin-top: 3.2rem;
      margin-bottom: 6.8rem;
      &__lists{
        margin-top: 1.2rem;
        margin-left: .8rem;
        &--child{
          margin-left: .8rem;
          .tr-contents-table__item{
            font-size: 1.6rem;
            margin-top: .4rem;
            line-height: 1.45;
          }
        }
      }
      &__item{
        font-size: 1.7rem;
        line-height: 1.428;
        font-weight: 400;
        padding-left: 0;
        &:first-of-type{
          margin-top: 0;
        }
      }
      &__button{
        left: -.8rem;
        border: {
          top: .8rem solid transparent;
          left: .8rem solid $mainColor;
          bottom: .8rem solid transparent;
        }
        &--clicked{
          left: 100%;
        }
      }
    }
  }
}

必要に応じて"table-of-contents"の部分をPHPの$html_class_block_nameに代入したクラスに書き換えてください。

JavaScriptでスクロールをスムーズにする

今回作成した目次は見出し部分をクリックすることで対応見出しに飛ぶことができるようになってますが、デフォルトだと少し使いにくいです。目次の目的の一つはユーザビリティ向上なので使いやすいようにしていきましょう。そこで見出しをクリックしたら対応見出しまでスムーズにスクロールされるコードを追加していきます。

/**
* smooth scroll function for link of table of contents
*/
window.addEventListener( 'DOMContentLoaded', () => {
  const ancLinks = document.querySelector( 'a[href^="#"]' );
  const ancLinksArray = Array.prototype.slice.call( ancLinks );

  ancLinksArray.forEach( link => {
    link.addEventListener( 'click', e => {
      e.preventDefault();
      const targetId = link.hash;
      const targetEl = document.querySelector( targetId );
      const targetOffset = window.pageYOffset + targetEl.getBoundingClientRect().top;
      window.scrollTo( {
        top: targetOffset,
        behavior: "smooth",
      } );
    } );
  } );
} );

このJavaScriptコードはアロー関数等ES6記法を取り入れているのでIEは対応外としています。

まとめ

今回は目次のSEO効果を考察し、使いやすい目次を生成できるプログラムを紹介しました。

目次の有無でSEOが爆発的に向上することはありません。そもそもSEO対策はこれをしなければいけない、というものではありません。そのページが訪問者にとって有意義な内容であることが検索順位を向上させるための最も効果的なことです。

訪問者に伝えたい情報を効果的に伝えるために、目次を設置したりトップに戻るボタンを追加したりと、いろんな細かいユーザビリティの向上がSEO対策に繋がります。