見出し目次を生成するTOCスクリプト

2019年02月06日
カスタマイズ
6
HTML CSS VanillaJS 初心者向け Instruction

以前にもTOCの記事は書いていますが今回jQuery依存なしのvanilla JS(ネイティブJS, pure JS)のご紹介と説明で統一しようと思います。私が今後制作するテンプレートはよほどのことが無い限りjQueryを入れないと思います。それなのにjQueryで紹介というのはやはりマズいもので。

今回は以前書いた内容に加え、より詳細な説明をしたいと思います。特に難しいわけではないのですが、FC2ブログでの導入 に的を絞ると結構コツというか特徴を押さえておかないと上手くいきません。ついでに複数のCSSスタイリングも用意しました。

以降の内容で「TOCを記載, TOCを掲載」と出てきましたらそれは「見出し目次リストを書き出すhtmlの記載」であり、「TOCが記載」されている場所がイコール「見出し目次リストが表示される場所」だと思ってください。

FC2ブログでTOCを利用するには「追記」に記載

これは 絶対条件 と思ってください。これまで何度か 全文タイプテンプレートの難点 について記事を書いています。TOCスクリプトを利用するにも全文の一定条件下ではちょっと都合が悪いんですね。

TOCは記事内の見出しを抽出してリスト化するとともに、各見出しへの ページ内移動 も目的としています。TOCを本文に書いてしまうと結果的に全文を「本文」に書かざるを得なくなります。TOCが「本文」に、見出しが「追記」に、と別れているとトップページで表示されるTOCの内容と個別記事のTOCの内容が変わってしまいます。

えと。この理屈わかりますよね (´・ェ・`)
テンプレートの仕様にもよりますが、通常の全文タイプは「追記」をトップページに表示しません。あるいは敢えて「追記」をトップページにも表示してある場合のほとんどは初期設定 display: none でのクリック展開型になっていると思います。前者の「追記」が無い場合には「追記」内の見出しは抽出されず、後者の展開型は見出しが展開要素の中にあるとTOCのページ内移動が行き場を失ってしまいます。従って全部を「本文」に書かざるを得なくなる、という理屈です。

つまり TOCはトップページでは表示しない のが原則。トップに表示しないためには「追記」に記す、と。そゆこと。全文タイプテンプレートで「追記」を利用しない方や「追記」を展開型にしている方は実はSEO面でもいろいろと不利なんですYO (((ง'ω')و三 ง'ω')ڡ≡

加えてこれまで何度も記事にしているように、各記事の全内容がトップで表示されるとユーザビリティを損ないますのでおすすめできない。従って以下の内容に当てはまる方はご利用頂けません。

  • 全文タイプテンプレートを利用しており、「追記」を利用していない
  • 全文タイプテンプレートを利用しており「追記」も使っているが「本文」でも見出しを使っている

要約タイプテンプレートをご利用の方はTOCを「本文」に記載してもセーフではありますが、全文タイプに変更した時に泣くことになるか諦めることになりますのでやはり「追記」への掲載を絶対条件にした方が良いですね。

見出しは「追記」で利用する

TOCを「追記」に記載するのですから当然見出しは全て「追記」の中でなければいけません。仮にTOCよりも前に見出しが存在した場合には Cannot read property 'appendChild' of null というエラーでリストは生成できませんので注意(要素を追加する際に目印になる要素が先行取得出来ない時にこのエラーになります)
つまり TOCよりも前に見出しを使ってはいけない ということです。

場合によってはTOC不要で見出しを使いたい場合があるかと思います。その場合には「この見出しとこの見出しはTOCに」「この見出しはTOCに含めない」ではなく、ページ単位(記事単位)で「TOCを使うか使わないか」の両極の選択を行うようにしてください。TOCが要らない場合にはTOC書き出し用のhtmlを記載しない、TOCが要る時はhtmlを各見出しよりも先に書く。それだけです。

導入手順

ここからは実際の導入手順です。

【TOCスクリプト】

!function(e){"use strict";var t=function(e,t){for(var n in t)t.hasOwnProperty(n)&&t[n]&&(e[n]=t[n]);return e},n=function(e,t){var n=[],r=document.querySelectorAll(t);return Array.prototype.forEach.call(r,function(t){var r=t.querySelectorAll(e);n=n.concat(Array.prototype.slice.call(r))}),n},r=function(e){if("string"!=typeof e)return 0;var t=e.match(/\d/g);return t?Math.min.apply(null,t):1},o=function(e,t){for(;t--;)e=e.appendChild(document.createElement("ol")),t&&(e=e.appendChild(document.createElement("li")));return e},c=function(e,t){for(;t--;)e=e.parentElement;return e},i=function(e,t){return function(n,r,o){var c=n.textContent,i=t+"-"+o;r.textContent=c;var a=e?i:n.id||i;a=encodeURIComponent(a),n.id=a,r.href="#"+a}},a=function(e){var t=e.selector,a=e.scope,u=document.createElement("ol"),l=u,f=null,d=i(e.overwrite,e.prefix);return n(t,a).reduce(function(e,t,n){var i=r(t.tagName),a=i-e;a>0&&(l=o(f,a)),a<0&&(l=c(l,2*-a)),l=l||u;var p=document.createElement("li"),m=document.createElement("a");return d(t,m,n),l.appendChild(p).appendChild(m),f=p,i},r(t)),u},u=function(e){var n={selector:"h1, h2, h3, h4, h5, h6",scope:"body",overwrite:!1,prefix:"chapter"};e=t(n,e);var r=e.selector;if("string"!=typeof r)throw new TypeError("selector must be a string");if(!r.match(/^(?:h[1-6],?\s*)+$/g))throw new TypeError("selector must contains only h1-6");var o=location.hash;return o&&setTimeout(function(){var e=document.getElementById(o.slice(1));e&&e.scrollIntoView()},0),a(e)};"function"==typeof define&&define.amd?define(function(){return u}):e.initTOC=u}(window);

TOC本体、リストを生成するためのJSです。async指定を推奨しますので、外部ファイル化を行いたい方はこれ以降の内容も含める必要があるため後述します。テンプレートhtmlへ直貼りする方は以下のような形で記載してください。
圧縮済なのでソース内に改行を含めないようにしてください。
自身の可読性のために適当に改行を入れてしまう方が多いのですが、改行にはルールがあります。詳細な説明は省きますがとりあえず今回は行わないようにしてください。

<!--permanent_area-->
<script>ここに上記内容をペースト</script>
<!--/permanent_area-->

テンプレート記載位置は </body> の直前でOKです。body開始タグではなくbody終了タグの方ですのでお間違いなく。

【デフォルト設定上書き用スクリプト】

var container = document.querySelector('#toc');
var toc = initTOC({
    selector: 'h2, h3, h4, h5, h6',
    scope: '#inner-contents',
    overwrite: true,
    prefix: 'chapter'
});
if(container) {
    container.appendChild(toc);(window);
}

id名やclass名など不確定要素については上書き(overwrite)できるようにしてあります。つまり 上書き内容 = 確認必須内容 です。各個人によって指定すべき値が異なります。

selecor の指定

記事内で利用する最上位の見出しから最下位の見出しを指定してください。最上位はテンプレート「記事部分」内で利用可能な見出しを要確認。

scope の指定

追記を包含する要素のid名またはclass名を確認して指定。<%topentry_more> で検索し、それを挟む要素のidまたはclass属性の値を記す。

例1) id属性

<!--more-->
<div id="postscript">
  <%topentry_more>
</div>
<!--/more-->
scope: '#postscript',

例2) class属性

<!--more-->
<div class="contents">
  <%topentry_more>
</div>
<!--/more-->
scope: '.contents',

例3) id, classいずれの属性も無い場合

<!--more-->
<%topentry_more>
<!--/more-->

この場合にはdiv要素を追加(囲う)するか、<%topentry_body><%topentry_more>双方を 包含している要素についているidまたはclass名を指定。

例2-1) div要素の追加( + クラス属性を付与)

<!--more-->
<div class="tsuiki">
  <%topentry_more>
</div>
<!--/more-->
scope: '.tsuiki',

例2-2) 本文, 追記双方を包含する要素のidまたはclass名

<div id="inner-contents">
  <%topentry_body>
  <!--more-->
  <%topentry_more>
  <!--/more-->
</div>
scope: '#inner-contents',

prefix の指定

各見出しにはid名が付与されます。そのid名をデフォルトでは chapter-数字 に設定していますが、chapter-数字 が既存の名称であった場合には変更してください(末尾の「ハイフン + 数字」は共通、変更不可です)

TOC本体スクリプトをテンプレートに直書きした方はその直下に記載

TOC本体スクリプトを外部化したい方はこの章の内容も含めてファイル化してください。できれば上書き用コードも圧縮します。

Minify JS and CSS online, or include the minifier in your project for on-the-fly compression.

Minify JS and CSS online, or include the minifier in your project for on-the-fly compression.

TOC本体JS内容
上書き用JS内容

本体コードの末尾で一度改行OKです。そして .js 拡張子で保存 → FC2サーバーへアップロード。

<script src="ここにJSファイルアドレス" async></script>

テンプレート記載位置は </body> 直前です。

外部化した場合にはテンプレート変更時に上書き内容を再度確認し、相違があれば新たにファイルを作成しなければいけません。単純に移設してしまわないよう注意。ファイル新規作成がめんどうな場合でそれでも本体を外部ファイル化しておきたい場合には

<script src="ここにTOC本体ファイルアドレス"></script>
<script>
ここに上書き用JSをインラインで記載
</script>

「インラインで記載」というのは外部ファイル化せずにJSコードを直書きすることです。本体は外部ファイル、上書き内容はインライン、という形。ただし async(非同期読み込み)の指定はできません のでページの表示スピードに多少影響が出ます。

【TOC書き出し用html】

<nav id="toc"></nav>

記事を書く際に「追記」に記載。特に変更などが生じない内容なので「もくじ」などで辞書登録しておくと簡単に呼び出せて便利です。このhtmlを記載すればTOCが表示され、記載しなければTOCは表示されません。

【「見出しリストに戻る」用html】

自動出力すると「TOCを表示したくない + 見出しを使いたい」という条件下でも戻るリンクだけが表示されてしまいますのでTOC本体スクリプトからは除外してあります。使いたい箇所に以下の内容を記載してください。「もどる」などで辞書登録しておくと良いでしょう。

<div class="back-toc" style="font-weight: bold;"><a href="#toc">目次へ戻る&uarr;</a></div>

【デフォルトCSS】

/* toc */
#toc {
  position: relative;
  width: 100%;
  margin: 30px auto;/* 30pxは先行要素と後続要素との距離 */
  padding: 3em 40px 0 0;
  border: 1px solid rgb(209,215,215);/* 全体を囲うボーダー */
  background: rgb(249,255,255);/* 全体背景色 */
  line-height: 1.5;/* 行間設定(注意 単位なしで記載) */
}

#toc:before {
  content: "目次";/* toc上部タイトル */
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 3em;
  font-weight: bold;
}

#toc a {
  display: block;
  width: 100%;
  padding: 5px 0;
  border-top: 1px solid rgb(229,235,235);/* 各リンク上部のボーダー */
  color: rgb(51,51,51);/* リンク色 */
  word-break: break-all;
  text-decoration: none;
}

/* 戻るリンクの指定 */
.back-toc {
  margin: 1em 0;
}

テンプレートスタイルシートの末尾に記載。

その他のCSSスタイリング

デフォルトのCSS内容と差し替えてください。同時記載ではなく差し替えです。

四角

/* toc 四角 */
#toc {
  width: 100%;
  margin: 30px auto;/* 30pxは先行要素と後続要素との距離 */
  line-height: 1.5;/* 行間設定(注意 単位なしで記載) */
}

#toc ol {
  list-style: none;
  counter-reset: li;
  margin-left: 2.5em;
  padding: 0;
}

#toc a {
  display: block;
  position: relative;
  width: 100%;
  padding: 5px 0;
  color: rgb(51,51,51);
  word-break: break-all;
  text-decoration: none;
}

#toc a::before {
  content: counter(li);
  counter-increment: li;
  position: absolute;
  left: -2.5em;
  top: 50%;
  margin-top: -1em;
  height: 2em;
  width: 2em;
  background: rgb(122,65,113);/* 最上級見出しの背景色 */
  color: white;
  font-weight: bold;
  line-height: 2em;
  text-align: center;
}

#toc ol ol a::before {
  background: rgb(188,100,164);/* 二位見出しの背景色 */
}

#toc ol ol ol a::before {
  background: rgb(196,163,191);/* 三位見出しの背景色 */
}

/* 戻るリンクの指定 */
.back-toc {
  margin: 1em 0;
}

円形にしたい場合は #toc a::beforeborder-radius: 50%; を追加。
3段階の位までスタイリングしてあります。

四角吹き出し

/* toc 四角吹き出し */
#toc {
  width: 100%;
  margin: 30px auto;/* 30pxは先行要素と後続要素との距離 */
  line-height: 1.5;/* 行間設定(注意 単位なしで記載) */
}

#toc ol {
  list-style: none;
  counter-reset: li;
  margin-left: 2.7em;
  padding: 0;
}

#toc a {
  display: block;
  position: relative;
  width: 100%;
  padding: 5px 0;
  color: rgb(51,51,51);
  word-break: break-all;
  text-decoration: none;
}

#toc a::before {
  content: counter(li);
  counter-increment: li;
  position: absolute;
  left: -2.7em;
  top: 50%;
  margin-top: -1em;
  height: 2em;
  width: 2em;
  background: rgb(122,65,113);/* 最上級見出しの背景色 */
  color: white;
  font-weight: bold;
  line-height: 2em;
  text-align: center;
}

#toc ol ol a::before {
  background: rgb(188,100,164);/* 二位見出しの背景色 */
}

#toc ol ol ol a::before {
  background: rgb(196,163,191);/* 三位見出しの背景色 */
}

#toc a::after {
  content: "";
  position: absolute;
  left: -.7em;
  top: 50%;
  border: .3em solid transparent;
  border-left-color: rgb(122,65,113);/* 最上級見出し吹き出し角背景色 */
  margin-top: -.3em;
}

#toc ol ol a::after {
  border-left-color: rgb(188,100,164);/* 二位見出し吹き出し角背景色 */
}

#toc ol ol ol a::after {
  border-left-color: rgb(196,163,191);/* 三位見出し吹き出し角背景色 */
}

/* 戻るリンクの指定 */
.back-toc {
  margin: 1em 0;
}

3段階の位までスタイリングしてあります。

リンクに背景色

/* toc リンク背景色 */
#toc {
  width: 100%;
  margin: 30px auto;/* 30pxは先行要素と後続要素との距離 */
  padding: 3em 40px 0 0;
  line-height: 1.5;/* 行間設定(注意 単位なしで記載) */
}

#toc a {
  display: inline-block;
  margin: .4em 0;
  padding: 5px 0;
  background: rgb(238,238,255);/* 最上位見出しリンク背景色 */
  color: rgb(51,51,51);/* リンク色 */
  word-break: break-all;
  text-decoration: none;
}

#toc ol ol a {
  background: rgb(238,255,238);/* 二位見出しリンク背景色 */
}

#toc ol ol ol a {
  background: rgb(255,255,221);/* 三位見出しリンク背景色 */
}

/* 戻るリンクの指定 */
.back-toc {
  margin: 1em 0;
}

3段階の位までスタイリングしてあります。

リンクにドット背景

/* toc リンクドット背景 */
#toc {
  width: 100%;
  margin: 30px auto;/* 30pxは先行要素と後続要素との距離 */
  padding: 3em 40px 0 0;
  line-height: 1.5;/* 行間設定(注意 単位なしで記載) */
}

#toc a {
  display: inline-block;
  margin: .4em 0;
  padding: 5px 0;
  background-color: rgb(238,238,255);/* 最上位見出しリンク背景色 */
  background-image: radial-gradient(rgb(208,208,225) 10%, transparent 20%), radial-gradient(rgb(208,208,225) 10%, transparent 20%);/* 最上位見出しドット色 */
  background-size: 20px 20px;
  background-position: 0 0, 10px 10px;
  color: rgb(51,51,51);/* リンク色 */
  word-break: break-all;
  text-decoration: none;
}

#toc ol ol a {
  background-color: rgb(238,255,238);/* 二位見出しリンク背景色 */
  background-image: radial-gradient(rgb(208,225,208) 10%, transparent 20%), radial-gradient(rgb(208,225,208) 10%, transparent 20%);/* 二位見出しドット色 */
}

#toc ol ol ol a {
  background-color: rgb(255,255,221);/* 三位見出しリンク背景色 */
  background-image: radial-gradient(rgb(225,225,191) 10%, transparent 20%), radial-gradient(rgb(225,225,191) 10%, transparent 20%);/* 三位見出しドット色 */
}

/* 戻るリンクの指定 */
.back-toc {
  margin: 1em 0;
}

3段階の位までスタイリングしてあります。

CSSデザインはそれこそ山程あります。数字に装飾を加える場合には counter-increment プロパティを利用するのが手っ取り早い方法なので、その雛形としてお使いください。

まとめ

TOCがあると固い印象を与えてしまいがちですがデザイン次第で柔らかくなります。GoogleはTOCを積極的に拾おうとしていますのでチャレンジしてみてください。

vanillaice (Akira)

Posted by vanillaice (Akira)

関連する記事

コメント 6

There are no comments yet.

toc  

2019/02/20 (Wed) 16:34

1.
1.1
1.1.1
みたいに表示させたいです…

toc  

2019/02/20 (Wed) 16:56

cssでできました。

-  

2019/05/27 (Mon) 17:45

管理人のみ閲覧できます

このコメントは管理人のみ閲覧できます

Akira  

2019/05/27 (Mon) 18:15
vanillaice (Akira)

To TOCの件 内緒さん

こんにちは ('0')/

> なぜか、中身が目次内に表示されなくて〜

<nav id="toc"></nav>

は記事編集画面に直接記載をしてください。TOCのscript実行よりもこのDOMが「先」に存在しないと書き出しができません。
そしてできれば見出しを含め、目次用nav要素も「追記」に記されると良いと思います。全文タイプテンプレートに変更したくともできなくなる可能性があるからです(トップページで障害が出ます)
よろしくお願いします。

Janedoe1471  

2019/05/27 (Mon) 18:26

To Akiraさん

素早いお返事、非常にありがたいです。
tocの位置をscriptの上に移動したら、できました。
今後、デザインを変えるとしても、全文タイプは選びませんし、雰囲気を変えたくなったら、今のテンプレを改造して変えるだけですので、私の場合は、これでOKです。
ものぐさで、忘れっぽいので、記事投稿のたびに、tocタグを追記に貼るという作業が、面倒なので、私には、このほうが向いています。
ありがとうございました。

Akira  

2019/05/28 (Tue) 01:41
vanillaice (Akira)

To Janedoe1471さん

修正できたということで安心しました。また、運営方針の件についても了解しました。
お疲れ様でした :)

コメント投稿

テンプレートに関するご質問・不具合のご報告の際はご自身のブログアドレス記載必須です。
ご質問の前に 必ずお読みください
テンプレートに関するご質問時のお願い

必ず該当テンプレートの専用記事にお願いします。無関係な記事・別のテンプレート専用記事でのコメントはお控えください。
テンプレートカテゴリ
テンプレート一覧(表示タイプ別)