ねかつちう。

お金や節約に敏感な20代30代のための物欲系雑記ブログ

【WordPress】アコーディオンが自動開閉する追従目次の作り方

[PR]このページにはアフィリエイトプログラムによる商品・サービス等の広告を掲載しています

Wordpressで高機能な追従目次を作成する方法を、備忘録を兼ねてシェアします。

スクロールにあわせてサイドバー内で目次が追従するほか、H2~H4タグをアコーディオンで入れ子にして、なおかつスクロールにあわせてアコーディオンが自動開閉します。

f:id:nekatsu:20210929043628p:plain

なお、以下で紹介するコードは、Creator Clipさんで公開されているものをベースに一部改変したものとなります。こちらのオリジナルのコードにアコーディオンの自動開閉機能を加えたのが本記事の特長となります。

DEMO

サンプルコード

ではまず、サンプルコードから。

JavaScript(jQuery)

以下のコードをJavaScriptファイルとして保存(メモ帳にコピペして拡張子.jsで保存)して、使用テーマ内のJavaScriptフォルダにアップしてください(無ければ、テーマ直下に「js」フォルダを作ってください)。

  1. $(function() {
  2.     /* -------------------------------------------------------
  3.         記事の見出しから目次作成
  4.     --------------------------------------------------------*/
  5.     function makeMokuji() {
  6.         var idcount = 1;
  7.         var mokuji = '';
  8.         var currentlevel = 0
  9.         var sectionTopArr = new Array();
  10.  
  11.         // 見出しを回してリストに格納
  12.         $('article h2, article h3, article h4').each(function(i){ //★★注
  13.  
  14.             // IDを保存
  15.             this.id = 'chapter-' + idcount;
  16.             idcount ++;
  17.  
  18.             // 見出しの入れ子
  19.             var level = 0;
  20.             if(this.nodeName.toLowerCase() == 'h2') {
  21.                 level = 1;
  22.             } else if(this.nodeName.toLowerCase() == 'h3') {
  23.                 level = 2;
  24.             } else if(this.nodeName.toLowerCase() == 'h4') {
  25.                 level = 3;
  26.             }
  27.             while(currentlevel < level) {
  28.                 mokuji += '<ol class="chapter">';
  29.                 currentlevel ++;
  30.             }
  31.             while(currentlevel > level) {
  32.                 mokuji += '</ol>';
  33.                 currentlevel --;
  34.             }
  35.  
  36.             // リストを生成
  37.             mokuji += '<li><a href="#' + this.id + '">' + $(this).html() + '</a></li>\n';
  38.         });
  39.         while(currentlevel > 0) {
  40.             mokuji += '</ol>';
  41.             currentlevel --;
  42.         } 
  43.  
  44.         // HTML出力
  45.         strMokuji = '<h4><span class="mokuji-color">目次</span>で流し読み <span class="kao">・*・:≡( ε:)</span> <span class="closeBtn"><i class="fa fa-times-circle-o"></i></span></h4>\
  46.                      <div class="mokujiInner">'
  47.                         + mokuji +
  48.                      '<!-- /.mokujiInner --></div>';
  49.                     
  50.         $('.mokuji').html(strMokuji);
  51.  
  52.         /* -------------------------------------------------------
  53.             リストクリックでスムーズスクロール
  54.         --------------------------------------------------------*/
  55.         $('.mokuji li').not('.accordion, .accBtn').click(function(){
  56.             var speed = 800;
  57.             var href = $(this).find('a').attr('href');
  58.             var target = $(href == '#' || href == '' ? 'html' : href);
  59.             var position = target.offset().top;
  60.             $('html, body').stop().animate({scrollTop:position}, speed, 'easeInOutCirc');
  61.             return false;
  62.         });
  63.  
  64.         /* -------------------------------------------------------
  65.             目次のアコーディオン
  66.         --------------------------------------------------------*/
  67.         $('.mokuji ol').prev().addClass('accordion').append('<span class="accBtn"><i class="fa fa-plus-square-o"></i></span>');
  68.         $('.mokuji li.accordion').wrapInner('<div class="inner clearfix"></div>');
  69.         // 開閉ボタンを押した時
  70.         $('.accBtn').click(function(){
  71.             // 開閉処理
  72.             $(this).parents('li').next().stop().slideToggle(300, 'easeInOutCirc');
  73.             // 閉じるボタンアイコン切替
  74.             $('.closeBtn').removeClass('active').addClass('active');
  75.             // アイコン切替
  76.             if( $(this).find('i').hasClass('fa-plus-square-o') ){
  77.                 $(this).find('i').removeClass('fa-plus-square-o').addClass('fa-minus-square-o');
  78.             } else {
  79.                 $(this).find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square-o');
  80.             }
  81.             return false;
  82.         });
  83.  
  84.         // 閉じるボタンの表示切替
  85.         var closeBtnFlag = '';
  86.         $('.mokuji li').each(function() {
  87.             if( $(this).hasClass('accordion') ) {
  88.                 closeBtnFlag = false;
  89.             }
  90.         });
  91.         if( closeBtnFlag == true ) {
  92.             $('.closeBtn').hide();
  93.         }
  94.  
  95.         // 全て閉じるボタンを押した時
  96.         $('.closeBtn').click(function(){
  97.  
  98.             // 閉じるアイコン切替
  99.             $(this).toggleClass('active');
  100.  
  101.             // classの有無を確認
  102.             if( $(this).hasClass('active') ){
  103.                 $('.mokuji ol ol').stop().slideDown(300, 'easeInOutCirc');
  104.                 $('.accBtn').find('i').removeClass('fa-plus-square-o').addClass('fa-minus-square-o');
  105.             } else {
  106.                 $('.mokuji ol ol').stop().slideUp(300, 'easeInOutCirc');
  107.                 $('.accBtn').find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square-o');
  108.             }
  109.         });
  110.  
  111.         /* -------------------------------------------------------
  112.             カレント表示切替
  113.         --------------------------------------------------------*/
  114.         var secTopArr = new Array();
  115.         secTopArr.length = 0;
  116.         var current = -1;
  117.  
  118.         // 現在位置の取得
  119.         $(window).on("load",function() {
  120.         $('article [id^="chapter"]').each(function(i){
  121.             secTopArr[i] = $(this).offset().top;
  122.         });
  123.         });
  124.  
  125.         //スクロールイベント
  126.         $(window).on('load scroll',function(){
  127.             for (var i = secTopArr.length-1; i>=0; i--) {
  128.                 if ($(window).scrollTop() > secTopArr[i] - 300) {
  129.                     $('.mokuji li').removeClass('current').eq(i).addClass('current');
  130.                     $('.mokuji ol ol li.current').parent('ol').prev().addClass('current');
  131.                     $('.mokuji ol ol li.current').parent().prev('li').addClass('current');
  132.  
  133. //★★スクロールダウンするとアコーディオンを開く
  134.         $.when(
  135.                     $('.mokuji li.current').next('ol').slideDown(300, 'easeInOutCirc')
  136.             ).done(
  137.             $('.current .accBtn').find('i').removeClass('fa-plus-square-o').addClass('fa-minus-square-o'),
  138.             $('.closeBtn').addClass('active')
  139.         )
  140.  
  141. // ★★★スクロールダウンで通過したアコーディオンを閉じる
  142.                     
  143.         $.when(
  144.                     $('.mokuji li.current').prev('ol').slideUp(300, 'easeInOutCirc')
  145.             ).done(
  146.             $('.accBtn').not('.current .accBtn').find('i').removeClass('fa-minus-square-o').addClass('fa-plus-square-o')
  147.         )
  148.  
  149. // ★★★スクロールアップで通過したアコーディオンを閉じる
  150.                     $('.mokuji li.current').next().next().next('ol').slideUp(300, 'easeInOutCirc');
  151.                     $('.mokuji li.current').next().next('ol').slideUp(300, 'easeInOutCirc');
  152.                     break;
  153.                 }
  154.             }
  155.         });
  156.     }
  157.     makeMokuji();
  158.     /* -------------------------------------------------------
  159.         目次固定
  160.     --------------------------------------------------------*/
  161.     function fixedSide() {
  162.         // ウィンドウ幅・人気記事を取得
  163.         var w = window.innerWidth;
  164.         var mainH = $('#main').height(); //★★注
  165.         var sideH = $('#side').height(); //★★注
  166.         var fixedElm = '';
  167.         
  168.         if(mainH > sideH) { // サイドバーより長ければ
  169.         
  170.             fixedElm = $('.mokuji');
  171.                             
  172.             // 要素の位置を取得
  173.             var fixedSideTop = fixedElm.offset().top;
  174.             var footerTop = $('footer').offset().top;
  175.             var scrollBottom = $('body').height() - $(window).height() - $('footer').outerHeight();
  176.             
  177.             $(window).scroll(function(){
  178.                 // スクロール位置を取得
  179.                 y = $(window).scrollTop();
  180.                 
  181.                 // スクロールがサイドバーを上回ったら
  182.                 if(y > fixedSideTop){
  183.                     fixedElm.addClass('fixed-side');
  184.                 } else {
  185.                     fixedElm.removeClass('fixed-side');
  186.                 }
  187.             });
  188.         }
  189.     }
  190.     fixedSide();
  191. });

131行目から151行目のあたりが、アコーディオンを自動開閉させるために、わたしが加筆したコードです。

スクロールにあわせてアコーディオンを自動開閉することはそれほど難しくないのですが、アコーディオンの各階層に開閉状態をあらわすアイコンがあり、それらを連動させるのに苦労しました。

もしうまく動かない場合は、★★注部分の要素名やクラス名を確認してください。使用しているWordPressごとに異なる場合があります。

《2021.10.6追記》

WordPress5.5から画像の遅延読み込みがデフォルトになった影響で、目次の現在位置表示がズレる事象が発生してしまうため、119行目あたりに「すべて読みこんだあとで位置関係を取得する」コードを追加しました。

HTML

bodyタグの末尾に以下のコードを記述してください。

JavaScriptファイルまでのパスは、実際のサイト構造に応じて記述してください。

  1. <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js"></script>
  2. <script src="wp-content/themes/テーマ名/js/XXXX.js"></script>

手短ながら以上となります。CSSは使用するサイトにより微調整が必要ですので、Creator Clipさんで公開されているコードを参考にされてください。