ブログで導入文のあとに設置されているとすごく便利な「目次ナビゲーション」。

この後何が語られるのかざっくりと先に頭に入れておくことができるし、クリックするとスクロールでその見出しまで移動してくれるとさらに便利。

もちろん当ブログでもこの「目次ナビゲーション」を設置しているのですが、実はこれ、プラグインで実装しているものではありません。

ただの好奇心でプラグインなしで作って見たのですが、JavaScirptだけで意外と簡単に実装できたので、その手順をメモっておこうと思います。

ほとんどコピペで実装できると思うので、プラグイン使うのは嫌だな〜って方は是非参考にしてみてください。

目次

準備1:目次のタグやブログの構造を確認する

まずは準備です。

目次にしたい見出しは何番のhタグを使っているのか

例えば、下の画像のように、大見出しは全てh2タグ、中見出しは全てh3タグで書いてあるパターン

画像

ほとんどこんな感じかと思いますが、たまに大見出しも全部h1タグだったりするので。

今回はこのパターンだった場合のスクリプトになります。自分で見出しのタグを調節できる人は、上の画像のような構造へしておいてください。

記事を囲っているdivタグなどのID名またはクラス名を確認する

見出しを探し出す対象範囲を限定するため、記事全体を包むラッパーのID名などを使用するので確認しておいてください。

画像

記事全体でなくとも、見出しが使われ始める本文の導入部以降が何かのタグで包まれている場合、それれを使うこともできます。

IDまたはクラスが付いている要素か、もしくはそのページで1回しか使われていないタグ(articleなど)で記事部分が包まれている必要があります。

今回のコードでは、#post_contentというid名がついたタグで記事が囲まれていると想定したものとします。

準備2:目次を表示させたい場所にIDを付けたdivタグを置いておく

目次ナビゲーションを表示させたい場所に空のdivタグを置いておきます。(以下、このdivタグを「目次ラッパー」と表記します。)

スクリプトで作成された目次をそのdivの中へ移すので、IDを付けておきましょう。

例として、#index_wrappというid名を付けておきます。

目次を表示したい場所に以下のようにdivタグを設置

<div id="index_wrapp">
  <!-- ここに生成したhtmlソースが入る -->
</div>

これを、「目次ラッパーA」と呼ぶことにします。

空タグの設置が気持ち悪い場合

もし空のタグを置いておくことに抵抗がある場合は先にspanタグなどを設置しておいてください。

先にspanタグなどを入れておく(hタグにはしないように)

<div id="index_wrapp">
  <span>目次</span>
  <!-- ここに生成したhtmlソースが入る -->
</div>

このパターンを「目次ラッパーB」と呼ぶことにします。

大見出し(h2タグ)のみを含んだ目次ナビゲーションの実装

さて、では実際のスクリプトを紹介していきます。

まずはシンプルに、「大見出し(h2タグ)だけを目次ナビゲーション表示する」場合を例にしてみます。

というのも、ブログごとに書き換える必要のある部分がいくつかあるので、このシンプルなコードを元にし、その辺りの補足説明をしておこうと思います。

何はともあれ、今回のスクリプトのベースとなるコードは以下の通りです。

h2タグのみを検出して目次ナビゲーションを生成するシンプルな実装

const $indexWrapp = document.getElementById('index_wrapp');  //目次ラッパー
if ($indexWrapp !== null) {
    /* 目次ラッパーが用意されていればスクリプトを実行 */
    const $postContent = getElementById('post_content');     //記事本文が書かれているラッパー
    const $h2 = $postContent.getElementsByTagName('h2');     //記事内のh2タグを全て取得

    if ($h2.length > 0) {
        /* h2タグがあれば */
        let indexHtml = "<span>目次</span><ul>";  //目次ラッパーの中身となるhtmlソース

        for (let i = 0; i < $h2.length; i++) {
            /* h2の数だけループを回し、ulタグの中身を生成します */
            $h2[i].setAttribute('id', "index_id" + i);  //リンクで飛べるようにIDをつける
            indexHtml += '<li><a href="#index_id' + i + '">' + $h2[i].textContent + '</a></li>';
        }

        indexHtml += '</ul>';
        $indexWrapp.innerHTML = indexHtml;

    } else {
        /* h2タグがなければ、目次は不要なので隠す */
        $indexWrapp.style.display = "none";
    }
}

補足事項

いくつか、補足を。

変数宣言について

変数宣言にはconstletを使っていますが、環境に合わせてvarに置き換えてください。

記事本文が書かれているラッパーをクラス名・タグから取得したい場合

上記コードの$postContentの取得部分について。

クラス名で取得する場合

//querySelectorで取得
const $postContent = document.querySelector('.post_content');

//getElementsByClassNameで取得する場合
let $postContent = document.getElementsByClassName('post_content');
$postContent = $postContent[0];  //getElementsByClassNameの返り値は配列なのでこうする

タグ名で取得する場合(articleを例に)

//querySelectorで取得
const $postContent = document.querySelector('article');

//getElementsByTagNameで取得する場合
let $postContent = document.getElementsByTagName('article');
$postContent = $postContent[0];  //getElementsByTagNameの返り値は配列なのでこうする

生成しているソースの中身について

目次ナビゲーションのhtmlソースが最終的に以下のようになります。

生成されるhtml

<div id="index_wrapp">
  <span>目次</span>
  <ul>
    <li><a>...</a><li> <!-- 目次の数だけ繰り返し -->
  </ul>
</div>

目次タイトルとなるspan要素を別物に変更したり、何か要素を追加したり、ulタグにクラスを付けたりしたい場合は、
let indexHtml = "<span>目次</span><ul>の分部分を好きに変更してみてください。

別パターン:生成されたhtmlソースをappendで目次ラッパーに挿入したい場合

先述のコードは、最後に.innerHTML = indexHtml(jQueryで言うと.html(indexHtml))を使用しています。

これは、目次ラッパーの中身のhtmlソースを丸々スクリプトで生成したソースに置き換えているので、目次ラッパーのタイプがAでもBでも同じ結果になります。

もし、そうではなく、目次ラッパーBのように先に中に要素を入れておき、その後ろにナビ部分だけを挿入したい場合は次のようにしてください。

目次ラッパーにappendでソースを挿入する場合のコード

const $indexWrapp = document.getElementById('index_wrapp');
if ($indexWrapp !== null) {
    /* 目次ラッパーが用意されていればスクリプトを実行 */
    const $postContent = document.etElementById('post_content');
    const $h2 = $postContent.getElementsByTagName('h2');

    if ($h2.length > 0) {
        /* h2タグがあれば */
        let indexList = document.createElement("ul");  //挿入するulエレメント作成
        let indexHtml = "";

        for (let i = 0; i < $h2.length; i++) {
            /* h2の数だけループを回し、ulタグの中身を生成します */
            $h2[i].setAttribute('id', "index_id" + i);
            indexHtml += '<li><a href="#index_id' + i + '">' + $h2[i].textContent + '</a></li>';
        }

        indexList.innerHTML = indexHtml;    //作成しておいたulエレメントに作成したhtmlソースを追加
        $indexWrapp.appendChild(indexList); //目次ラッパーにulエレメントをappend

    } else {
        /* h2タグがなければ、目次は不要なので隠す */
        $indexWrapp.style.display = "none";
    }
}

*変更箇所だけ補足コメントを入れています

注意事項

これ以降のコード例でも、上記の補足事項やappendのパターンについては同じことが言えます。

繰り返しになってしまうのでこれ以降では補足説明は省略します。各環境に合わせて書き換えてみてください。

 

大見出し(h2タグ)と中見出し(h3タグ)を含んだ目次ナビゲーションの実装

さて、こちらが本番コードです。h3タグまで目次ナビに含めます。

h2・ h3タグを検出して目次ナビゲーションを実装するスクリプト

const $indexWrapp = document.getElementById('index_wrapp');  //目次ラッパー
if ($indexWrapp !== null) {
    /* 目次ラッパーが用意されていればスクリプトを実行 */
    const $postContent = document.getElementById('post_content');  //記事本文が書かれているラッパー
    const $index = $postContent.querySelectorAll('h2, h3');        //記事内の見出しタグを全て取得

    if ($index.length > 0) {
        /* 見出しタグがあれば */
        let indexHtml = '<span>目次</span><ul>';  //目次ラッパーの中身となるhtmlソース

        for (let i = 0; i < $index.length; i++) {
            /* 見出しタグの数だけループを回し、ulタグの中身を生成します */
            let $thisIndex = $index[i];                     //$index[i]を何度か使うので先に変数に代入しておく
            $thisIndex.setAttribute('id', "index_id" + i);  //リンクで飛べるようにIDをつける

            if ($thisIndex.tagName === 'H2') {

                indexHtml += '<li><a href="#index_id' + i + '">' + $thisIndex.textContent + '</a></li>';

            } else if ($thisIndex.tagName === 'H3') {

                indexHtml += '<li class="child"><a href="#index_id' + i + '">' + $thisIndex.textContent + '</a></li>';

            }
        }

        indexHtml += '</ul>';
        $indexWrapp.innerHTML = indexHtml;

    } else {
        /* h2タグがなければ、目次は不要なので隠す */
        $indexWrapp.style.display = "none";
    }
}

*補足説明は「大見出し(h2タグ)のみを含んだ目次ナビゲーションの実装」にて行なっていますので、そちらを参考にしてください。

おまけ:リンク先への移動をアニメーションさせる

特に何もしなければ、リンク先への移動は瞬間移動してしまいますが、これをアニメーションさせるとさらにいい感じになります。

いわゆるスムーススクロールってやつですね。

 $('a[href^="#"]').click(function(){
  var speed = 800, //スピード
      href = $(this).attr("href"),
      target = $(href === "#" || href === "" ? 'html' : href),
       position = target.offset().top; //スクロールさせる位置
   $("html, body").animate({scrollTop:position}, speed, "swing");
   return false; //忘れずに
});

*  jQuery使っちゃってますが、おまけ部分なのでご了承ください。jQueryなしで実装出来次第、修正いたします。

このスクリプトの中身について以下の記事で少し触れているので、気になった方は調べてみてください。

[href^=#]とはなんぞや。ページ内リンクをスムーススクロールさせる時に記述しているあれ[href^=#]とはなんぞや。ページ内リンクをスムーススクロールさせる時に記述しているあれ

また、ヘッダーをdisplay: fixed;で固定している場合は、ヘッダー分の高さを変数positonから引いておきましょう。 

おわりに

jQueryなしで目次ナビゲーションを自動生成するスクリプトでした。

ぜひ使ってみて下さい。

動かねーぞ、といったことがございましたらぜひコメントまたはお問い合わせください。

 

- Thank you for reading. -

コメント

コメントする