[Blogger] 目次を簡単に自動生成(忙しい人向けのコピペ素材)

2022年1月7日金曜日

Blog Blogger TOC 目次自動生成

t f B! P L

enter image description here

この記事では、Bloggerで目次を自動生成する方法を、紹介します。

Bloggerは、無料なのに「広告表示なし」「カスタムドメイン可」「アドセンスも可」など、非常に素晴らしいブログサービスです。

しかし、WordPressの様に、プラグインをインストール出来ない為、Bloggerで用意されている標準ウェジットの機能だけでは機能不足を感じる事があります。

その内の一つに、Bloggerには目次を自動生成する機能がありません。
※ WordPressの場合、Table of Contents Plus (TOC+)というプラグインをインストールすれば、目次が自動生成できます。

今回、Bloggerで目次を自動生成するブラグインを作成しました。
このプラグインを導入する事で、WordPressの「TOC+」プラグインの様に、簡単に目次を自動生成させる事が出来る様になります。

実際、この記事でも下 に表示されている目次は、今回紹介するプラグインで自動生成させています。

スポンサーリンク

特徴

  • 見出しタグ(h2,h3,h4…)を自動的に検出して、目次を自動生成
  • jQuery不使用 (プレーンな JavaScirpt)
  • スムーススクロールに対応
  • 目次の表示/非表示のリンクボタン付き
  • 階層的にヘッダタグ(h2,h3,h4)を組んでも目次化が可能
  • 軽量で圧縮された CSS, JavaScript
  • オプションで目次の表示内容が制御可能
  • 段落番号を自動付与
  • Google検索結果のスニペットに目次リンクを表示

プラグインの導入

テーマをバックアップ

念のため、現在のテーマをバックアップしましょう。

Bloggerの管理画面から、[テーマ] -> [カスタマイズ] -> [バックアップ] をクリックして、現在のテーマをダウンロードしてバックアップしておきましょう。

もし間違って操作して、テーマを壊してしまった場合は、このダウンロードしたバックアップから復元することができます。

HTMLを編集

  1. テーマの画面で、[カスタマイズ] -> [HTMLの編集] をクリックし、HTMLを表示します
  2. </head>の直前に、以下のコードを追加します
<!-- [START] 目次作成プラグイン-->
<b:if cond='data:blog.pageType == "item"'>
  <script>
    //以下のオプションを好みに合わせて変更して下さい
    //オプションの詳しい説明は、(https://www.sukerou.com/2018/10/blogger-table-of-contents-javascript.html)を参照
    var toc_options = {
      target: ["h2", "h3", "h4"],
      autoNumber:  true,
      condTargetCount: 2,
      insertPosition: "firstHeadBefore",
      showToc: true,
      width: "auto",
      marginTop: "20px",
      marginBottom: "20px",
      indent: "20px",
      postBodySelector: ".widget.Blog"
    };

    //これ以降のソースは編集しないでください
    ;(function (window) { var id_seq= 0; document.addEventListener(&apos;DOMContentLoaded&apos;, function () { var rootElement= document.querySelector(toc_options.postBodySelector); if (rootElement== null || typeof rootElement=== &quot;undefined&quot;) { return;} if (toc_options.target.length== 0) return; rootContent= searchHeadLine(toc_options, rootElement); if (rootContent.children.length &gt;= toc_options.condTargetCount) { var wrap= createElement(rootContent); appendElement(wrap);}}); function searchHeadLine(toc_options, rootElement) { var count= toc_options.target.length; var fn= function (index, element, parentContent) { var currentTarget= toc_options.target[index]; var nextTarget= index &lt; count - 1 ? toc_options.target[index + 1] : &quot;&quot;; var id= &quot;toc_headline_&quot; + (++id_seq); var content= createItem(currentTarget, text(element), index + 1, id); parentContent.children.push(content); element.id= id; var el= next(element); if (nextTarget== &quot;&quot;) { return;} var prevTarget= &quot;&quot;; for(var i= index; i &gt;= 0; i--) { prevTarget += (toc_options.target[i] + &quot;,&quot;);} while (true) { if (el== null || typeof el=== &quot;undefined&quot;) break; if (tagName(el)== currentTarget) break; if (tagName(el)== nextTarget) { fn(index + 1, el, content);} else { var nextElements= el.querySelectorAll(prevTarget + nextTarget); var breakFlg= false; for (var i= 0; i &lt; nextElements.length; i++) { if (tagName(nextElements[i]) != nextTarget) { exitFlg= true; break;} fn(index + 1, nextElements[i], content);} if (breakFlg) break;} var el= next(el);}}; var rootContent= createItem(&quot;ROOT&quot;, &quot;&quot;, 0); var elements= rootElement.getElementsByTagName(toc_options.target[0]); for (var i= 0; i &lt; elements.length; i++) { fn(0, elements[i], rootContent, &quot;&quot;);} return rootContent;} function createElement(rootContent) { var wrap= document.createElement(&quot;div&quot;); wrap.classList.add(&quot;b-toc-container&quot;); wrap.style.marginTop= toc_options.marginTop; wrap.style.marginBottom= toc_options.marginTop; if (toc_options.width== &quot;100%&quot;) { wrap.style.display= &quot;block&quot;;} else { wrap.style.width= toc_options.width;} var p= document.createElement(&quot;p&quot;); var span1= document.createElement(&quot;span&quot;); var span2= document.createElement(&quot;span&quot;); var span3= document.createElement(&quot;span&quot;); span2.classList.add(&quot;b-toc-show-wrap&quot;); span3.classList.add(&quot;b-toc-show-wrap&quot;); var a= document.createElement(&quot;a&quot;); span1.innerText= &quot;目次&quot;; span2.innerText= &quot;[&quot;; span3.innerText= &quot;]&quot;; a.href= &quot;javascript:void(0);&quot;; p.appendChild(span1); p.appendChild(span2); p.appendChild(a); p.appendChild(span3); var toggleToc= function (state) { var s= typeof state=== &quot;boolean&quot; ? state : hasClass(wrap, &quot;hide&quot;); if (s) { a.innerText= &quot;非表示&quot;; wrap.classList.remove(&quot;hide&quot;);} else { a.innerText= &quot;表示&quot;; wrap.classList.add(&quot;hide&quot;);}}; a.addEventListener(&apos;click&apos;, toggleToc); toggleToc(toc_options.showToc); var ul= document.createElement(&quot;ul&quot;); ul.classList.add(&quot;toc-root-list&quot;); rootContent.children.forEach(function (content, index) { createContentItemElement(ul, content, (index + 1) + &quot;&quot;);}); wrap.appendChild(p); wrap.appendChild(ul); return wrap;} function createContentItemElement(ul, content, no) { var li= document.createElement(&quot;li&quot;); li.classList.add(&quot;toc-list-item&quot;); var a= document.createElement(&quot;a&quot;); li.style.paddingLeft= toc_options.indent; ul.style.paddingLeft= 0; a.href= &quot;#&quot; + content.id; smoothScroll(a); if (toc_options.autoNumber) { var spanNm= document.createElement(&quot;span&quot;); spanNm.classList.add(&quot;toc-number&quot;); spanNm.innerText= no + &quot;.&quot;;} var spanText= document.createElement(&quot;span&quot;); spanText.classList.add(&quot;toc-text&quot;); spanText.innerText= content.text; if (toc_options.autoNumber) a.appendChild(spanNm); a.appendChild(spanText); li.appendChild(a); ul.appendChild(li); if (content.children.length &gt; 0) { var childUl= document.createElement(&quot;ul&quot;); childUl.classList.add(&quot;toc-sub-list&quot;); li.appendChild(childUl); content.children.forEach(function (childContent, index) { createContentItemElement(childUl, childContent, no + &quot;.&quot; + (index + 1));});}} function smoothScroll(a) { a.addEventListener(&apos;click&apos;, (e)=&gt; { e.preventDefault(); let href= a.getAttribute(&apos;href&apos;); let targetElement= document.getElementById(href.replace(&apos;#&apos;, &apos;&apos;)); const rect= targetElement.getBoundingClientRect().top; const offset= window.pageYOffset; const target= rect + offset - 0; window.scrollTo({ top: target, behavior: &apos;smooth&apos;, });});} function appendElement(element) { var el= null; var rootElement= document.querySelector(toc_options.postBodySelector); if (toc_options.insertPosition== &quot;firstHeadBefore&quot; || toc_options.insertPosition== &quot;firstHeadAfter&quot;) { el= rootElement.querySelector(toc_options.target[0]);} else if (toc_options.insertPosition== &quot;top&quot;) { el= rootElement;} if (el== null) return; if (toc_options.insertPosition== &quot;firstHeadBefore&quot;) { before(el, element);} else if (toc_options.insertPosition== &quot;firstHeadAfter&quot;) { after(el, element);} else if (toc_options.insertPosition== &quot;top&quot;) { before(el, element);}} function createItem(tagName, text, nestLevel, id) { return { tagName: tagName, text: text, children: [], nestLevel: nestLevel, id: id
};} function text(element) { return element.innerText;} function next(element) { return element.nextElementSibling;} function prev(element) { return element.previousElementSibling;} function tagName(element) { return element.tagName.toLowerCase();} function hasClass(element, className) { return element.classList.contains(className);} function parentElement(element) { return element.parentNode;} function after(element, insertElement) { var parent= parentElement(element); var nextEl= next(element); if (parent != null &amp;&amp; nextEl != null) { parent.insertBefore(insertElement, nextEl);}} function before(element, insertElement) { var parent= parentElement(element); if (parent != null) { parent.insertBefore(insertElement, element);}} })(window); 
  </script>
  <style type="text/css">
     .b-toc-container{background:#f9f9f9;border:1px solid #aaa;padding:10px;margin-bottom:1em;width:auto;display:table;font-size:95%}.b-toc-container p{text-align:center;margin:0;padding:0}.b-toc-container ul{list-style-type:none;list-style:none;margin:0;padding:0}.b-toc-container>ul{margin:15px 0 0}.b-toc-container.hide>ul{display:none}.b-toc-container ul li{margin:0;padding:0 0 0 20px;list-style:none}.b-toc-container ul li:after,.b-toc-container ul li:before{background:0;border-radius:0;content:""}.b-toc-container ul li a{text-decoration:none;color:#008db7!important;font-weight:400;display:flex;align-items:flex-start;flex-wrap:nowrap}.b-toc-container ul li .toc-number{margin:0 .5em 0 0;white-space:nowrap}.b-toc-container ul li .toc-text:hover{text-decoration:underline}
  </style>
</b:if>
<!-- [END] 目次作成プラグイン-->

コードの挿入位置は、以下のイメージを参考にして下さい。
コードの挿入位置

  1. コードを貼り付けたら、画面上の [テーマを保存] をクリックして、内容を保存します。

以上で、プラグインの導入は完了です。

保存が終わったら、自分のブログを見て下さい。
↓のようなイメージで、目次が表示されていると思います。

目次の表示イメージ

オプション

今回紹介した、目次自動生成プラグインは、WordPressの「TOC+ 」同様、オプションで、目次の表示条件を制御する事ができます。

ここでは、オプションの設定方法と、各オプションの説明を行います。

オプションの設定方法

コピーしたコードに、toc_options = {…} と書かれている部分があります。
この JSON を直接編集して、目次の表示オプションを変更します。

オプションの設定内容は、後述する「各オプションの説明」を参照して下さい。

   //以下のオプションを好みに合わせて変更して下さい
   var toc_options = {
     target: ["h2", "h3", "h4"],
     autoNumber: true,
     condTargetCount: 2,
     insertPosition: "firstHeadBefore",
     showToc: true,
     width: "auto",
     marginTop: "20px",
     marginBottom: "20px",
     indent:  "20px",
     postBodySelector: ".widget.Blog"
   };

各オプションの説明

target

目次を作成する、見出しタグを h1〜h6の 範囲で指定します。

autoNumber

目次に、自動的に連番を付けるか指定します。

true を指定した場合、1 → 1.1 → 1.1.1の順に、階層化された連番を自動的に付与します。
false 連番を表示しません。

condTargetCount

目次を表示する、見出の数を指定します。
例えば 2 を指定した場合、targetオプションで指定した、トップレベルの見出しが、2つ以上あるとき、目次が表示されます。

insertPosition

目次の表示位置を、以下の3つから指定します。

目次の表示位置
firstHeadBefore 最初の見出しの前
firstHeadAfter 最初の見出しの後
top 記事の最上部

showToc

初期の目次表示状態を指定します。

true を指定した場合、初期状態で目次を表示します。
false を指定した場合、目次は閉じた状態になります。

width

目次の横幅を指定します。

auto 目次の文字数に合わせて、横幅を自動調節
○○% 横幅をパーセンテージで指定 (100%で横幅一杯に目次を表示)
○○px 横幅をピクセル単位で指定

marginTop

目次上部の余白を指定します。

marginBottom

目次下部の余白を指定します。

indent

目次のインデント幅を指定します。

インデントの幅が指定可能

※以前コメントで、インデントを設定したいと要望貰った為、オプションを追加してみました。

postBodySelector

※ このオプションはCSSセレクタやJavaScriptに詳しい方だけが、変更する事をお勧めします。

目次の作成範囲となる、記事本文のタグをCSSセレクタで指定します。
記事本文以外に、見出しタグ(h2〜h4)が使われている場合、このオプションを設定する事で、記事本文内にある見出しタグだけで、目次を作成できます。

目次のデザインを変更する

直接CSSを編集すれば、デザインを変更する事ができますが、コピペで簡単に使えるデザインを、いくつか用意しました。
使い方は、<style type="text/css">…</style>となっている部分を、各デザインのCSSに置き換えて下さい。

鮮やかなドット枠の目次

鮮やかなドット枠の目次の表示イメージ

CSS

<style type="text/css">
  .b-toc-container{background:#f1f8ff;border:dashed 2px #668ad8;padding:10px;margin-bottom:1em;width:auto;display:table;font-size:95%}.b-toc-container p{text-align:center;margin:0;padding:0}.b-toc-container ul{list-style-type:none;list-style:none;margin:0;padding:0}.b-toc-container>ul{margin:15px 0 0}.b-toc-container.hide>ul{display:none}.b-toc-container ul li{margin:0;padding:0 0 0 20px;list-style:none}.b-toc-container ul li:after,.b-toc-container ul li:before{background:0;border-radius:0;content:""}.b-toc-container ul li a{text-decoration:none;color:#668ad8!important;font-weight:700;display:flex;align-items:flex-start;flex-wrap:nowrap}.b-toc-container ul li .toc-number{margin:0 .5em 0 0;white-space:nowrap}.b-toc-container ul li .toc-text:hover{text-decoration:underline}
</style>

上下のみボーダーの目次

上下のみボーダーの目次の表示イメージ

<style type="text/css">
.b-toc-container{border-top:solid #1e366a 1px;border-bottom:solid #1e366a 1px;padding:10px;margin-bottom:1em;width:auto;display:table;font-size:95%}.b-toc-container p{text-align:center;font-weight:700;margin:0;padding:0}.b-toc-container ul{list-style-type:none;list-style:none;margin:0;padding:0}.b-toc-container>ul{margin:15px 0 0}.b-toc-container.hide>ul{display:none}.b-toc-container ul li{margin:0;padding:0 0 0 20px;list-style:none}.b-toc-container ul li:after,.b-toc-container ul li:before{background:0;border-radius:0;content:""}.b-toc-container ul li a{text-decoration:none;color:#1e366a!important;font-weight:700;display:flex;align-items:flex-start;flex-wrap:nowrap}.b-toc-container ul li .toc-number{margin:0 .5em 0 0;white-space:nowrap}.b-toc-container ul li .toc-text:hover{text-decoration:underline}
</style>

スティチ風の目次

スティチ風の目次の表示イメージ

<style type="text/css">
  .b-toc-container{padding:10px;margin-bottom:1em;width:auto;display:table;font-size:95%;background:#58be89;line-height:1.3em;border:2px dashed #fff;border-radius:10px;box-shadow:0 0 0 4px #58be89,2px 1px 6px 4px rgba(10,10,0,.5);text-shadow:-1px -1px #238452}.b-toc-container p{text-align:center;font-weight:400;color:#fff;margin:0;padding:0}.b-toc-container ul{list-style-type:none;list-style:none;margin:0;padding:0}.b-toc-container>ul{margin:15px 0 0}.b-toc-container.hide>ul{display:none}.b-toc-container ul li{margin:0;padding:0 0 0 20px;list-style:none}.b-toc-container ul li:after,.b-toc-container ul li:before{background:0;border-radius:0;content:""}.b-toc-container ul li a{text-decoration:none;color:#fff!important;font-weight:400;display:flex;align-items:flex-start;flex-wrap:nowrap}.b-toc-container ul li .toc-number{margin:0 .5em 0 0;white-space:nowrap}.b-toc-container ul li .toc-text:hover{text-decoration:underline}
</style>

付箋風な目次

付箋風な目次の表示イメージ

<style type="text/css">
  .b-toc-container{padding:0;margin-bottom:1em;width:auto;display:table;font-size:95%;position:relative;border:1px solid #333;background:#333}.b-toc-container p{position:absolute;top:46%;left:5px;text-align:center;margin:0;padding:0;width:1rem;color:#fff}.b-toc-container p .b-toc-show-wrap,.b-toc-container p a{display:none}.b-toc-container ul{list-style-type:none;list-style:none;margin:0;padding:0}.b-toc-container>ul{margin:0 0 0 1.5rem;padding:10px;border-left:1px solid #333;background:#fff}.b-toc-container.hide>ul{display:none}.b-toc-container ul li{margin:0;padding:0 0 0 20px;list-style:none}.b-toc-container ul li:after,.b-toc-container ul li:before{background:0;border-radius:0;content:""}.b-toc-container ul li a{text-decoration:none;color:#1e366a!important;font-weight:700;display:flex;align-items:flex-start;flex-wrap:nowrap}.b-toc-container ul li .toc-number{margin:0 .5em 0 0;white-space:nowrap}.b-toc-container ul li .toc-text:hover{text-decoration:underline}
</style>

最後に

自分の周りでも、WordPress を使っている人が多いですが、今回のような便利なプラグインで、Blogger を使う人が、1人でも増えれくれると、嬉しいです。

Blogger でこんな機能も欲しいよーー! という方がいれば、コメント欄で教えてください。

関連記事

BloggerのHTMLを徹底紹介!テーマをフルカスタマイズしてオリジナルブログを作る!)

Blogger のテーマを自分好みにフルカスタマイズしたい人に、オススメの記事です。
テーマの編集方法、Blogger 固有タグの紹介、簡単なデザインのサンプルを、紹介していきます。

Bloggerで「このブログの自分のビューを追跡しない」が効かない(チェックが外れる)時の対処方法)

Bloggerで、 [このブログの自分のビューを追跡しない]をチェックにしたにも関わらず、自分のアクセスがカウントされていたので、その問題の解決方法です。

最後に

自分の周りでも、WordPress を使っている人が多いですが、今回のような便利なプラグインで、Blogger を使う人が、1人でも増えれくれると、嬉しいです。
Blogger でこんな機能も欲しいよーー! という方がいれば、コメント欄で教えてください。

(追記)
Bloggerで、「アドセンス広告を記事中の好きな場所に挿入するスクリプト」という物を最近作りました。
こちにで使い方等について紹介していますので、よかったら見ていって下さい。

(2020/05/18 追記)
目次のテキストを折り返し位置が、自動連番の左位置とかぶっていたため、かぶらないように修正しました。

もし良かったら、この記事をリンクと共に紹介してくれると嬉しいです。

スポンサーリンク
スポンサーリンク

このブログを検索

Profile

自分の写真
Webアプリエンジニア。 日々新しい技術を追い求めてブログでアウトプットしています。
プロフィール画像は、猫村ゆゆこ様に書いてもらいました。

仕事募集もしていたり、していなかったり。

QooQ