JavaScript

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

最終更新日:

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

実装結果デモ

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

説明

HTML

  1. <section class="toc-demo">
  2. <h2>見出し1</h2>
  3. <p>
  4. 省略します。
  5. </p>
  6. <h2>見出し2</h2>
  7. <p>
  8. 省略します。
  9. </p>
  10. <h2>見出し3</h2>
  11. <p>
  12. 省略します。
  13. </p>
  14. <h2>見出し4</h2>
  15. <p>
  16. 省略します。
  17. </p>
  18. <h2>見出し5</h2>
  19. <p>
  20. 省略します。
  21. </p>
  22. </section>

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

CSS

  1. section.toc-demo{
  2. width: 800px;
  3. margin: 0 auto;
  4. }
  5. h2{
  6. font-size: 20px;
  7. background-color: #f3f3f3;
  8. border-bottom: 3px solid #333;
  9. padding: 5px 0 5px 10px;
  10. }
  11. p{
  12. padding-left: 10px;
  13. }
  14. ol{
  15. width: 150px;
  16. position: fixed;
  17. top: 0;
  18. right: 0;
  19. }
  20. li{
  21. list-style-type:none;
  22. }
  23. a{
  24. display: block;
  25. width: 100%;
  26. background-color: rgba(70 70 70 / 70%);
  27. color: #fff;
  28. margin-bottom: 1px;
  29. text-decoration: none;
  30. padding: 3px 5px 3px 10px;
  31. }
  32. a.current{
  33. background-color: #fff;
  34. color: #000;
  35. }

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

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

JavaScript

  1. const headings = document.querySelectorAll('h2');
  2. let toc = '<ol id="fixed-toc">';
  3. headings.forEach( function(heading, index) {
  4. heading.id = 'heading-0' + (index+1);
  5. let id = heading.id;
  6. toc = toc + '<li><a href="#' + id + '">' + heading.textContent + '</a></li>';
  7. });
  8. toc = toc + '</ol>';
  9. document.querySelector('.toc-demo').insertAdjacentHTML('afterbegin', toc);
  10. window.addEventListener('scroll', function() {
  11. let scroll = window.scrollY;//スクロール量を取得
  12. let hight = window.innerHeight;//画面の高さを取得
  13. let offset = 200;
  14. const toc_completed = document.getElementById('fixed-toc');
  15. headings.forEach( function( heading , index) {
  16. let i = index + 1;
  17. let target = document.querySelector('#fixed-toc li:nth-of-type(' + i + ') > a');
  18. let pos = heading.getBoundingClientRect().top + scroll;//見出しの位置
  19. if ( scroll &gt;pos - hight + offset ) {//スクロール量が見出しを超えた
  20. if (headings[index + 1] !== undefined){// 次の見出しがある=最後の見出しではない
  21. let next_pos = headings[index + 1].getBoundingClientRect().top + scroll;//次の見出しの位置
  22. if ( scroll &gt; next_pos - hight + offset ) { // スクロール量が次の見出しも超えている
  23. target.classList.remove('current');
  24. } else if (target.classList.contains('current') == true) { // すでにcurrentがついている
  25. return;
  26. } else if ( i == 1 &amp;&amp; toc_completed.classList.contains('active') == false ){// 1つ目
  27. target.classList.add('current')
  28. toc_completed.classList.add('active')
  29. } else { // 次の見出しは見えてない
  30. target.classList.add('current');
  31. }
  32. } else { //最後の見出しの時
  33. target.classList.add('current');
  34. }
  35. } else { //スクロール量が見出しを超えてない
  36. target.classList.remove('current');
  37. if ( i == 1 &amp;&amp; toc_completed.classList.contains('active')){ //1つ目に到達していない場合
  38. toc_completed.classList.remove('active')
  39. }
  40. }
  41. });
  42. });
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してもらえると、励みになります!