Bloggerで簡単な目次を表示してみた

ブログを読むとき、記事の目次を使っていますか?

どうせ最後まで読むから、とさっさとページをスワイプする人もいるかもしれません。でもときどき本文を読み始める前に、このページ上に知りたい情報が存在するか確かめたいこともあります。

WordPressで管理されているブログには目次がついているイメージがありますね。ラベルを押すと開閉して収納できるタイプをよく見かけます。「便利そう」「ちょっとかっこいい」と思っていました。でもこのブログで利用しているBloggerには、目次を表示してくれる公式ガジェットがありません。

今回はこのブログで利用しているBloggerに、簡易的な目次を表示する方法をまとめました。

目次の仕様

  • JavaScript使用(jQueryなど外部リソース無し)
  • 見出しは<h2>を想定
  • 記事内に<h2>が5つ以上あるときだけ目次生成
  • 表示する場所は1番最初にくる見出しの上

実際の記事中には<h3>以降の小見出しも存在します。しかしhtmlタグが入れ子になると分かりづらくなるので、今回は<h2>だけ扱います。

目次を自動生成するスクリプト

目次を使うのは記事ページだけです。Bloggerのif文でpageTypeがitemのときだけscriptが挿入されるようにします。

<b:if cond='data:blog.pageType == "item"'>
<script>
  var div, title, ol;
  // Bloggerで記事を表示するwidgetのIdを利用する
  var article = document.getElementById("Blog1");
  // 記事中のh2を全取得する
  var headings = article.getElementsByTagName("h2");
  
  // Bloggerでは比較演算子をエスケープする
  if(headings &amp;&amp; headings.length &gt; 4) {

    div = document.createElement("div");
    div.setAttribute("class", "toc");

    title = document.createElement("p");
    title.textContent = "目次";

    ol = document.createElement("ol");

    // リスト作成
    [].forEach.call(headings, function(e) {
      var li = document.createElement("li");
      var anchor = document.createElement("a");
      anchor.textContent = e.textContent;
      anchor.href = "#" + e.textContent;
      li.appendChild(anchor);
      ol.appendChild(li);
    })
   
    // div.tocにタイトルとリストを入れる
    div.appendChild(title);
    div.appendChild(ol);

    // div.tocを1番目の見出しの前に挿入する   
    headings[0].parentNode.insertBefore(div, headings[0]);

  }
</script>
</b:if>

上記のコードを</body>の前に貼り付けます。
その結果、1番目の見出しのすぐ上に目次が表示されます。開発者ツールで確認すると、以下のようなタグが生成されることが分かります。

<div class="toc">
  <p>目次</p>
  <ol>
    <li><a href="#見出し1">見出し1</a></li>
    <li>...</li>
  </ol>
</div>

このブログで実際使用しているコードは上記のものと異なります。デザインの手間を省きたかったため、「目次」というラベルを<h2>で作成して、リストの前に挿入しています。装飾する場合は目次と分かるclassつきのdivに入れておくと便利です。

この記事は見出しの数が少ないので表示されませんが。

余談

リストを動的に生成する方法を調べていて覚えたことなどをメモしておきます。主にJavaScriptについてです。

getElementsByTagName()とquerySelectorAll()のちがい

記事中の<h2>を全取得するときどちらを使うか。

  • getElementsByTagName(“h2”) -> HTMLCollection
  • querySelectorAll(“h2”) -> NodeList

戻り値は両方ともオブジェクト。ちがいは中身が動的に変更されるかどうか。HTMLCollectionは取得後に対象の要素が変更されると、動的に最新の状態に更新されます。NodeListは変更されません。

createDocumentFragment()について

複数のnodeを1つにまとめて、一括でDOMに追加できます。

今回はリスト自体を作成するところから始めたので使いませんでした。すでに描画されているリストに、大量の<li>appendChild()するときなどに利用するといいようです。

参考リンク(Document.createDocumentFragment() - Web API インターフェイス | MDN

Vue.jsで目次を生成してみる

最初は目次の生成にVue.js(JavaScriptフレームワーク)を使っていました。目次はページ上部に設置しているため、できる限り表示完了までのタイムラグを緩和したいところです。

Blogger上で、素のJavaScriptとVue.jsで目次生成してみると前者の方が早く表示できます。目次という単機能のためだけにVue.jsを使うのは大げさなのかもしれません。

ただし目次の生成と並行して、目次の下や見出しの上に広告を挿入したり、関連記事を取得して表示したり、タブメニューの中身を切り替えるなど、使いどころが複数あるならもっと活用できると思います。