2019-06-19

【jQuery不要】JS で章番号 & リンク付きの目次を自動生成する

WordPress 製のサイトなどで見かける目次。 そのページの内容がリストとしてひと目で確認できるため、Web サイトにも大きな役割を果たします。 JavaScript で目次を自動生成する方法を紹介します。

↓この目次のことです!

    JavaScript で目次を作る

    基本的に見出しは h1〜h6 でタグ付けされているかと思います。 これらのタグには見出しという情報だけでなく、見出しどうしの階層関係も表現されています。 よって、h1〜h6 タグは目次を作るのに最適な情報源と言えます。

    目次を置く場所に <ol class="table-of-contents"></ol> を配置して、この要素のなかに JavaScript で目次の情報を書き込んでいきます。

    上記が目次を自動生成する JavaScript の完成形になります。

    QuerySelectorAll で h1〜h6 タグを探す

    const table_of_contents = document.querySelectorAll(".table-of-contents");
    const headings = table.parentElement.querySelectorAll("h1, h2, h3, h4, h5, h6");
    

    まずは .table-of-contents を設置した親要素から、h1〜h6 要素を探し出します。

    h1〜h6 タグを元に目次要素を生成

    const li = document.createElement("li");
    const a = document.createElement("a");
    const id = `section-${table_of_contents_counter++}`;
    const ol = document.createElement("ol");
    
    // 目次要素の生成
    a.textContent = heading.textContent;
    a.href = `#${id}`;
    li.appendChild(a);
    li.appendChild(ol);
    

    探し出したh1〜h6 タグが変数 heading に入ってループ処理されているとします。 まずは目次1行分に相当する li 要素を生成し、そのなかに a 要素と ol 要素を入れています。 a 要素には h1〜h6 のテキストを入れて、さらにアンカーリンクとして href 属性に #section-${table_of_contents_counter++} を入れています。 変数 table_of_contents_counter は1、2、3…と増分していくので、生成されるアンカーリンクも #section-1、#section-2、…となります。 ol 要素は、見出しの階層構造を作るために後で必要となります。

    h1〜h6 タグにアンカーリンクも紐付け

    // リンク先の生成
    heading.id = id;
    heading.classList.add("anchor-link");
    

    生成したアンカーリンクを h1〜h6 タグの id に設定し、目次の要素をクリックしたときにページ内ジャンプするようにします。 さらに、必要であれば anchor-link クラスを付けて CSS でデザイン調整しましょう。

    階層構造の生成

    // 階層構造の生成
    const level = Number(heading.tagName.substring(1));
    let parent;
    do {
        parent = stack.pop();
    } while (parent.level >= level);
    parent.element.appendChild(li);
    stack.push(parent);
    stack.push({ level: level, element: ol });
    

    最後に、生成した li 要素を目次に挿入します。 このとき、h1〜h6 タグのレベル(h1→1、h2→2、…)に応じて、li 要素の挿入先が変わります。

    たとえば、h1 タグの次に h2 タグがある場合は、h1 タグの見出しの配下に h2 タグがある、と判定します。 このレベルの比較によって、どの目次要素の配下に新しい li 要素を挿入するかを、上記 4〜7 行目で行っています。

    CSS で見出しの番号を付ける

    JavaScript で生成した目次には何の CSS も適用していないため、見出しの番号が正しく付かないと思います。

    1. h1 タグ
      1. h2 タグ
      2. h2 タグ
    2. h1 タグ
    

    これを、CSS で正しく番号を振るようにします。

    .table-of-contents {
      max-width: 640px;
      margin-left: auto;
      margin-right: auto;
      border: 2px solid rgba(0,0,0,0.1);
      padding: 0.5rem 1rem;
    }
    .table-of-contents::before {
      content: "目次";
      font-weight: 700;
    }
    .table-of-contents ol {
      counter-reset: ol-counter;
    }
    .table-of-contents ol li {
      position: relative;
      list-style: none;
    }
    .table-of-contents ol li::before {
      counter-increment: ol-counter;
      content: counters(ol-counter,".");
      padding-right: 0.5rem;
      position: absolute;
      top: 0;
      left: -40px;
      display: inline-block;
      text-align: right;
      width: 40px;
    }
    
    1 h1 タグ
      1.1 h2 タグ
      1.2 h2 タグ
    2 h1 タグ
    

    デフォルトの番号付けを無効化

    まずはデフォルトの番号付けを無効します。

    .table-of-contents ol li {
      list-style: none;
    }
    

    counters で番号を作る

    .table-of-contents ol {
      counter-reset: ol-counter;
    }
    .table-of-contents ol li::before {
      counter-increment: ol-counter;
      content: counters(ol-counter,".");
    }
    

    CSS には counters という、番号を自動で振る便利な機能があります。 ol 要素を番号振りの原点として、以降は li 要素が来るたびにカウンタを +1 して表示しています。 その他、目次のデザインを整えるために position: absolute; とかしていますが、ここでは省略いたします🙇

    1からサイトを作るのは楽しい

    WordPress を使えば、目次を生成するプラグインもありますしデザインも洗練されています。 しかし、1からサイトを作るのも、JavaScript や CSS の勉強としていいかなと思います! 私の場合は、1から作ろうとしてつまづいたことが、そのまま記事として書けて良い循環になっています😊

    この記事の執筆者

    hato6502

    hata  

    中学生の頃に 6502 という CPU からプログラミングの世界に入りました。 C/C++ で1から作ることも好きですが、最近は変化の激しい IT 業界をみて Web 系にシフトしつつあります。