JavaScriptで記事の目次を生成する方法

schedule Published
Category folderJumble
format_list_bulleted Contents

JavaScriptで簡単に記事の目次を作る方法を紹介します。
このブログ記事もJavaScriptで目次を生成しています。
JSファイルだけなので、CMSやプラグインなどの影響がなく、一番手軽な実装じゃないかと思います。

JavaScriptを調べる準備

非エンジニアがコードをいきなり書くのは無理なので、JSにやってほしいことを整理します。
今のところエンジニアを目指してはいないので、必要なものだけを調べる準備となります。

JSに自動でやってほしいこと

このブログの作りのように、h2タグを目次にする前提として整理しています。

  • ページ内のh2を全て取得
  • 取得したh2の数に応じて実行
    • 目次にするli要素を生成
    • 生成した目次のli要素に、h2から取得したテキストを追加する
    • h2に重複しないid属性を追加
    • 目次のli要素の子となるa要素を生成
    • 生成したa要素のhref属性に、h2要素に追加したid属性を追加
    • li要素の子要素にa要素を挿入
    • ページ内のul要素に生成したli要素を挿入

必要なコードを調べる

ページ内のh2を取得

document.querySelectorAll()を使います。
該当する要素を全て取得して、配列っぽいオブジェクトを返します。ここで注意なのは配列ではないということです。 console.logで出力すれば分かりますが、NodeListと出力されます。

また、document.getElementsByTagName()を使っても取得できます。
こちらも配列っぽいオブジェクトを返しますが、こちらはHTMLCollectionとなり配列ではありません。

NodeListとHTMLCollectionの違い

ドキュメントを見ると、document.querySelectorAll()で取得したNodeListは静的 (ライブではない) 、HTMLCollectionは動的(生きたもの)とあります。また後で関連しますが、オブジェクトが違うのでメソッドも違っていることに注意です。

取得したh2の数だけ繰り返す

ページ内のh2の取得方法によって変わるケースもあります。

NodeListもHTMLCollectionも配列風のオブジェクトですが、「NodeListはArrayとは異なりますが、forEach() メソッドで処理を反復適用することは可能」とのことです。
HTMLCollectionの方はforEach() メソッドを使えないようです。NodelistはforEach() が使えるとはありますが、IE11でサポートされていなかったので、これまでは使われることが少なかったかもしれません。

一番素直な方法としては、 NodeListにもHTMLCollectionにもプロパティにlengthがあるので、数を取得してfor文で処理を繰り返せば問題なさそうです。今は気にしなくてよくなりましたが、IE11でも動作します。

こっちは邪道っぽい感じですが、Array.prototype.forEach.call(obj, …)を使えば、どちらでも使え、IE11でも動作します。callで無理やり使っちゃおうという感じで、ワイルドですね。

forEach()を使いたい人はひと手間が嫌だからだと思いますが、配列風なのを配列に変換すれば、forEach()が使えてIE11でも問題なく動作します。他にも方法はありそうですが、こういったところはJavaScriptを調べるときのやっかいなところです。

HTML要素の生成

document.createElement()を使って生成します。

要素に属性を設定する

プロパティを使う方法とメソッドelement.setAttributeを使う方法があります。
今回はプロパティを使いました。

テキストの取得・追加

NodeのプロパティtextContentを使います。

混同しやすいプロパティにinnerTextinnerHTMLがありますが、それぞれのプロパティを見ておくとよいと思います。

また、そもそものHTMLのDOMを理解できていない場合は、The HTML DOM APIopen_in_newのプロパティやメソッドの継承などを見ながら調べると分かりやすいかもです。

親要素に子要素を挿入する

今回は繰り返し処理で順番に追加するので、末尾に追加するNode.appendChild()メソッドを使います。

繰り返し処理などでリアルDOMに要素を1つずつ挿入すると、都度ブラウザの再フローが起きるので、DocumentFragmentを使って一括で挿入します。DocumentFragmentは、document.createDocumentFragment()メソッドを使って生成します。

コードを書く(サンプルコード紹介)

調べた内容を使ったサンプルコードです。
サンプルではfor文を使っています。

外部JSの場合はdefer属性を使って読み込むなどで、実行タイミングを調整してください。

おわりに

このブログでは目次を固定表示しているので、カレント機能やスクロール率の表示なども追加しました。

まずやりたいことの整理ができれば、非エンジニアでも調べることが簡単になります。
ドキュメントを読んでおくと、ちょっとずつできることが増えて応用がきくので、ソースをコピーするだけよりオススメです。