ホームページの脇に固定で目次を表示し、現在見ているところをハイライトで示すものについて、jQueryなしでJavaScriptで作る方法をご紹介します。

スクロールに応じて現在地表示する固定目次をJavaScriptで作る(jQueryなし)

実装結果デモ

まずは実装した結果をご確認ください。
スクロールしていくと、その時に表示されている見出しがハイライトされます。
固定表示されている目次で見出しをクリックすれば、そこにスクロールします。

See the Pen TOC by Takahiro Inada (@tkhr1) on CodePen.

説明

HTML

<section class="toc-demo">
  <h2>見出し1</h2>  
    <p>
        省略します。    
   </p>
  <h2>見出し2</h2>  
    <p>
        省略します。    
    </p>
  <h2>見出し3</h2>
    <p>
        省略します。    
    </p>
  <h2>見出し4</h2>  
    <p>
        省略します。    
    </p>
  <h2>見出し5</h2>  
    <p>
        省略します。    
    </p>  
</section>

後でお示しするJavaScriptでは、h2要素を拾って目次にします。
目次を挿入する箇所として、ここではclass="toc-demo"とつけられたsection要素の開始タグ直後とします。section要素は、他の要素でも大丈夫です。

CSS

section.toc-demo{
  width: 800px;
  margin: 0 auto;
}

h2{
  font-size: 20px;
  background-color: #f3f3f3;
  border-bottom: 3px solid #333;
  padding: 5px 0 5px 10px;
}
p{
  padding-left: 10px;
}
ol{
  width: 150px;
  position: fixed;
  top: 0;
  right: 0;
}
li{
  list-style-type:none;
}
a{
  display: block;
  width: 100%;
  background-color: rgba(70 70 70 / 70%);
  color: #fff;
  margin-bottom: 1px;
  text-decoration: none;
  padding: 3px 5px 3px 10px;
}
a.current{
  background-color: #fff;
  color: #000;
}

機能的な意味がある点としては、

17~19行目:この記述で右上に固定しています。
33~37行目:後述するJavaScriptで、現在表示されている見出しにclass="current"のクラス名をつけるので、その部分の見た目をここで変えています。

JavaScript

const headings = document.querySelectorAll('h2');
let toc = '<ol id="fixed-toc">';

headings.forEach( function(heading, index) {
  heading.id = 'heading-0' + (index+1);
  let id = heading.id;
  toc = toc + '<li><a href="#' + id + '">' + heading.textContent + '</a></li>';
});

toc = toc + '</ol>';
document.querySelector('.toc-demo').insertAdjacentHTML('afterbegin', toc);

window.addEventListener('scroll', function() {
    let scroll = window.scrollY;//スクロール量を取得
    let hight = window.innerHeight;//画面の高さを取得
    let offset = 200;
    const toc_completed = document.getElementById('fixed-toc');

    headings.forEach( function( heading , index) {
        let i = index + 1;
        let target = document.querySelector('#fixed-toc li:nth-of-type(' + i + ') &gt; a');
        let pos = heading.getBoundingClientRect().top + scroll;//見出しの位置

        if ( scroll &gt;pos - hight + offset ) {//スクロール量が見出しを超えた

            if (headings[index + 1] !== undefined){// 次の見出しがある=最後の見出しではない

                let next_pos = headings[index + 1].getBoundingClientRect().top + scroll;//次の見出しの位置
                if ( scroll &gt; next_pos - hight + offset ) { // スクロール量が次の見出しも超えている
                    target.classList.remove('current');
                } else if (target.classList.contains('current') == true) { // すでにcurrentがついている
                    return;
                } else if ( i == 1 &amp;&amp; toc_completed.classList.contains('active') == false ){// 1つ目
                    target.classList.add('current')
                    toc_completed.classList.add('active')
                } else { // 次の見出しは見えてない
                    target.classList.add('current');
                }

            } else { //最後の見出しの時
                target.classList.add('current');
            }
        } else { //スクロール量が見出しを超えてない
            target.classList.remove('current');
            if ( i == 1 &amp;&amp; toc_completed.classList.contains('active')){ //1つ目に到達していない場合
                toc_completed.classList.remove('active')
            }
        }
    });
});
1行目:ページ内に存在するh2をすべい拾い配列headingsに格納します。
2行目:挿入する目次のHTMLを変数tocに記述します。ここではまず、ol要素の開始タグのみ格納しています。
33~37行目:配列headingsに格納されている見出しそれぞれに、heading-01, heading-02というようにid名を付しています。
7行目:2行目で宣言した変数に、目次の中身となるliをここで格納しています。
10~11行目:
  • どの時点で、現在表示されている見出しとするかは16行目の変数offsetの値で調整します。
  • 現在表示されていると判断された見出しにclass="current"をつけています。
  • 上にスクロールしていくときの判断も必要なため、少し複雑なコーディングになっています。コメントアウトでコード中に補足をしているので、どのようにしているか具体的にはそのあたりから読み取ってください。

まとめ

固定表示して、現在表示されている見出しをハイライトする目次の作り方をご紹介しました。
1ページ1テーマであるブログなどではこのような目次にする必要はないと私は思いますが、長めのページで、人によっては後の方の内容だけに興味をもって見る可能性があるようなページの場合に、このような目次にしてみてもよいかなと思っています。

もし参考になった、という方いればSHAREしてもらえると、励みになります!

著者のイメージ画像

BringFlower|稲田 高洋

2003年から大手総合電機メーカーでUXデザインプロセスの研究、実践。UXデザイン専門家の育成プログラム開発。SEOにおいても重要なW3Cが定めるWeb標準仕様策定にウェブアクセシビリティの専門家として関わる。2010~2018年に人間中心設計専門家を保有。その後、不動産会社向けにSaaSを提供する企業の事業開発部で複数サービスを企画、ローンチ。CMSを提供し1000以上のサイトを分析。顧客サポート、サイト運営にも関わる。2022年3月にBringFlowerを開業し、SEOコンサル、デザイン、ウェブ制作を一手に受ける。グッドデザイン賞4件、ドイツユニバーサルデザイン賞2件、米国IDEA賞1件の受賞歴あり。