【JavaScript】JavaScriptで目次を作成
コンテンツ
はじめに
日記と勉強のノートの記事しかありませんでした…
今回はプログラミングに関しての記事です。
JavaScriptで見出しから目次を作成
何かを検索する毎に出てくるページのほとんどに目次(toc…Table Of Contents)があるのに、自分のページに設置していないなと思っていました。あまりコードを書いたりしていなかったので、外部のライブラリなどではJavaScriptで作ってみました。
完成のコードは以下です。簡単に説明すると、h1~h6のタグを探しのそのリスト(配列)から<ul><li><a>を用いて見出しへのリンクを作成します。詳しくはコードの下に記載します。
// ======================= 定数 ======================= const TOC_INSERT_TAG_ID = "#toc"; // tocを挿入するタグのID const HEADING_TAG = "h1,h2,h3,h4,h5,h6"; // 検索する見出しのタグ const ID_NAME = "articleHeading"; // リンク作成用のID名(ここに数字を足して一意にする) const TOC_A_CLASS_NAME = "tocLink"; // styleシート適用用 const LIST_TYPE = "ul" // Tocのリストの種類 // ======================= 変数 ======================= let IdSuffixNum = 0; // ======================= 関数 ======================= // リンク作成用の関数 function createUniqueId () { return ID_NAME + String(IdSuffixNum++); } // <li><a>見出し</a></li>を返す関数 // headingEl : 見出しのhX要素 function createLink (headingEl) { let li = document.createElement("li"); let a = document.createElement("a"); headingEl.id = createUniqueId(); a.href = "#" + headingEl.id; a.innerText = headingEl.innerText; a.className = TOC_A_CLASS_NAME; li.appendChild(a); return li; } // Tocを作成する関数 // parentEl : Tocを追加する要素 // headingList : tocにするhXの配列 function createToc(parentEl, headingList){ let preRank = 0; let liAddEl = null; for(let i = 0; i < headingList.length; i++){ let elRank = Number(headingList[i].tagName.substring(1)); // hXのX if (elRank >= preRank){ if (liAddEl === null){ // 最初の処理 liAddEl = document.createElement(LIST_TYPE); parentEl.appendChild(liAddEl); // tocを挿入する要素の子要素としてついか for (let j = 0; j < (elRank - preRank - 1); j++){ liAddEl.appendChild(document.createElement(LIST_TYPE)); liAddEl = liAddEl.lastElementChild; } }else{ // liAddElは<li><a>見出し</a></li>を"子要素として追加する"ol(ul)要素 // liAddElを的確に操作していく // 直前のhと操作の対称のhのランク差だけliAddElに<ul></ul>を追加して遷移 for (let j = 0; j < (elRank - preRank); j++){ liAddEl.appendChild(document.createElement(LIST_TYPE)); liAddEl = liAddEl.lastElementChild; } } }else{ // 直前のhと操作の対称のhのランク差だけliAddElを親要素の方向に遷移 for (let j = 0; j < (preRank - elRank); j++){ liAddEl = liAddEl.parentElement; } } // liを追加・直前のランクを更新 liAddEl.appendChild(createLink(headingList[i])); preRank = elRank; } } // ======================= Main ======================= const tocInsertElement = document.querySelector(TOC_INSERT_TAG_ID); const headingElements = document.querySelectorAll(HEADING_TAG); // console.log(tocInsertElement); // console.log(headingElements); createToc(tocInsertElement, headingElements); //console.log(tocInsertElement); //console.log(headingElements);
以下で定数を決めています。TOC_INSERT_TAG_ID
はHTMLの方に目次を挿入するために設置しておいたタグを探すための定数です。bodyのすぐ後ろに挿入するとかも考えられますが、目次はここに入れると決まっていることが多いと思うのでタグを設置してそこに挿入します。HEADING_TAG
はHTMLの見出し部分を探すための定数で、ここを変更すると目次にするヘッダータグを変えられます。LIST_TYPE
は目次にする際に使用するリストのタグの種類です。<ol>にすれば番号付きになります。
// ======================= 定数 ======================= const TOC_INSERT_TAG_ID = "#toc"; // tocを挿入するタグのID const HEADING_TAG = "h1,h2,h3,h4,h5,h6"; // 検索する見出しのタグ const ID_NAME = "articleHeading"; // リンク作成用のID名(ここに数字を足して一意にする) const TOC_A_CLASS_NAME = "tocLink"; // styleシート適用用 const LIST_TYPE = "ul" // Tocのリストの種類以下の部分が目次を作成する関数です(他の関数は説明略)。目次を挿入する場所のタグの要素(parentEl)と見出しの要素の配列(headingList)を受け取り目次を作成します。実装の方針は各見出しの要素に対して、そのひとつ前の要素とのランク(hXタグののX部分、preRankで管理)差から目次の<li><a>見出し</a></li>を挿入する場所を
liAddEl
で管理して、挿入するような感じです。
for(let i = 0; i < headingList.length; i++){}
文で見出しの各要素(headingList)を処理します。if (elRank >= preRank){}else{}
文でひとつ前の要素とのランク差からfor (let j = 0; j < (elRank - preRank); j++){}
でliAddElを適切に更新してliAddEl.appendChild(createLink(headingList[i]));
で<li><a>見出し</a></li>を追加します。
function createToc(parentEl, headingList){
let preRank = 0;
let liAddEl = null;
for(let i = 0; i < headingList.length; i++){
let elRank = Number(headingList[i].tagName.substring(1)); // hXのX
if (elRank >= preRank){
if (liAddEl === null){ // 最初の処理
liAddEl = document.createElement(LIST_TYPE);
parentEl.appendChild(liAddEl); // tocを挿入する要素の子要素としてついか
for (let j = 0; j < (elRank - preRank - 1); j++){
liAddEl.appendChild(document.createElement(LIST_TYPE));
liAddEl = liAddEl.lastElementChild;
}
}else{
// liAddElは<li><a>見出し</a></li>を"子要素として追加する"ol(ul)要素
// liAddElを的確に操作していく
// 直前のhと操作の対称のhのランク差だけliAddElに<ul></ul>を追加して遷移
for (let j = 0; j < (elRank - preRank); j++){
liAddEl.appendChild(document.createElement(LIST_TYPE));
liAddEl = liAddEl.lastElementChild;
}
}
}else{
// 直前のhと操作の対称のhのランク差だけliAddElを親要素の方向に遷移
for (let j = 0; j < (preRank - elRank); j++){
liAddEl = liAddEl.parentElement;
}
}
// liを追加・直前のランクを更新
liAddEl.appendChild(createLink(headingList[i]));
preRank = elRank;
}
}
以下の部分で、定義した定数・変数・関数で目次を追加します。
const tocInsertElement = document.querySelector(TOC_INSERT_TAG_ID);
で目次を挿入するタグを探します。次にconst headingElements = document.querySelectorAll(HEADING_TAG);
で目次にするヘッダを探して配列にします。目次を挿入する要素に対してcreateToc(tocInsertElement, headingElements);
で目次を作成して挿入します。
// ======================= Main ======================= const tocInsertElement = document.querySelector(TOC_INSERT_TAG_ID); const headingElements = document.querySelectorAll(HEADING_TAG); createToc(tocInsertElement, headingElements);
以下のHTMLに適用した例をその下に記載しました。
<div id="toc"></div> <h2>見出し1</h2> <h3>見出し1の小見出し1</h3> <h3>見出し1の小見出し2</h3> <h4>見出し1の小見出し2の小見出し1</h4> <h4>見出し1の小見出し2の小見出し2</h4> <h3>見出し1の小見出し3</h3> <h2>見出し2</h2> <h4>見出し2の小見出し1</h4> <h4>見出し2の小見出し2</h4> <script src="make_toc.js"></script>
生成される目次の要素
<div id="toc"> <ul> <ul> <li><a href="#articleHeading0" class="tocLink">見出し1</a></li> <ul> <li><a href="#articleHeading1" class="tocLink">見出し1の小見出し1</a></li> <li><a href="#articleHeading2" class="tocLink">見出し1の小見出し2</a></li> <ul> <li><a href="#articleHeading3" class="tocLink">見出し1の小見出し2の小見出し1</a> </li><li><a href="#articleHeading4" class="tocLink">見出し1の小見出し2の小見出し2</a></li> </ul> <li><a href="#articleHeading5" class="tocLink">見出し1の小見出し3</a></li> </ul> <li><a href="#articleHeading6" class="tocLink">見出し2</a></li> <ul> <ul> <li><a href="#articleHeading7" class="tocLink">見出し2の小見出し1</a></li> <li><a href="#articleHeading8" class="tocLink">見出し2の小見出し2</a></li> </ul> </ul> </ul> </ul> </div>
ページの見た目(CSSは未設定)