2020-04-19   8fd309f9

【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 から作ろうとしてつまずいたことが、そのまま記事として書けて良い循環になっています 😊

    この記事の執筆者

    hata6502

    Tomoyuki Hata   Follow @hata6502

    中学生の頃に 6502 という CPU からプログラミングの世界に入りました。 C/C++ で1から作ることも好きですが、最近は Web 系に移行しつつあります。