今回は、目次をセクションの見出しから自動生成する機能を素のJSを使って、実装してみます。
目次を作成するにあたっての仕様
以下の仕様にしています。
この仕様に沿って、実際に目次を作成してみましょう。
目次の完成形
目次の完成形がこちらになります。
See the Pen サイドバー固定の目次を自動生成する by レメ (@grozxntv-the-sasster) on CodePen.
目次を実際に作ってみよう
基本的な流れは以下の通りです。
- ulタグの直下にliタグとaタグを生成する。
- アンカーリンクをh2タグのid属性に付与し、リンクを追加
- セクションタイトルごとに目次アイテムを作成し、目次リストアイテムを一度にDOMに追加
HTMLは以下で準備します。
<div class="container">
<section class="sec">
<h2 class="sec--ttl">タイトル1</h2>
<p class="sec--txt">ここにテキストが入ります。</p>
</section>
<section class="sec">
<h2 class="sec--ttl">タイトル2</h2>
<p class="sec--txt">ここにテキストが入ります。</p>
</section>
<section class="sec">
<h2 class="sec--ttl">タイトル3</h2>
<p class="sec--txt">ここにテキストが入ります。</p>
</section>
<section class="sec">
<h2 class="sec--ttl">タイトル4</h2>
<p class="sec--txt">ここにテキストが入ります。</p>
</section>
<section class="sec">
<h2 class="sec--ttl">タイトル5</h2>
<p class="sec--txt">ここにテキストが入ります。</p>
</section>
</div>
<div class="toc" id="toc">
<div class="toc--inner">
<ul class="toc--list"></ul>
</div>
<!-- /.toc--inner -->
</div>
CSSは、今回適当ですが、滑らかにスクロールさせるために、scroll-behavior: smooth;
を適用しています。
html {
scroll-behavior: smooth;
}
body {
margin: 0;
box-sizing: border-box;
height: 3000px;
}
.container {
position: relative;
}
.sec--ttl {
font-size: 32px;
}
.sec {
height: 400px;
background-color: rgb(205, 205, 205);
border: 1px solid black;
}
.toc {
position: fixed;
top: 100px;
right: 0;
background-color: skyblue;
padding: 16px;
}
.toc--list_item {
padding: 1em;
}
ulタグの直下にliタグとaタグを生成する
まずは、セレクタを変数に格納しましょう。
.sec
と.sec--ttl
のクラスを持つ要素は今回、複数あるので、querySelectorAll
で取得しましょう。
// セレクタを変数に格納する
const container = document.querySelector('.container');
const sections = document.querySelectorAll('.sec');
const sectionTitles = document.querySelectorAll('.sec--ttl');
const tocList = document.querySelector('.toc--list');
ulタグの直下にliタグとaタグを生成するためには、createElementメソッドを使いましょう。
作成したら、appendChildメソッドでulタグの子ノードリストの末尾にノードを追加します。
const tocListItem = document.createElement('li');
const tocListLink = document.createElement('a');
tocListItem.appendChild(tocListLink);
アンカーリンクをh2タグのid属性に付与し、リンクを追加
setAttributeメソッドを使って、id属性にsec–ttl1,sec–ttl2,sec–ttl3と付与していく。
function createTOCItem(title, index) {
title.setAttribute('id', `sec--ttl${index + 1}`);
const titleId = title.getAttribute('id');
tocListItem.classList.add(`toc--list_item${index + 1}`, 'toc--list_item');
tocListItem.appendChild(tocListLink);
tocListLink.setAttribute('href', `#${titleId}`);
}
上記の手順を組み合わせて、目次リストアイテムを作成する関数が完成。
// 目次リストアイテムを作成する関数を定義
function createTOCItem(title, index) {
title.setAttribute('id', `sec--ttl${index + 1}`);
const titleId = title.getAttribute('id');
const titleText = title.textContent;
// 目次リストアイテムを作成し、リンクを追加
const tocListItem = document.createElement('li');
const tocListLink = document.createElement('a');
tocListLink.textContent = titleText;
tocListItem.classList.add(`toc--list_item${index + 1}`, 'toc--list_item');
tocListItem.appendChild(tocListLink);
tocListLink.setAttribute('href', `#${titleId}`);
return tocListItem;
}
セクションタイトルごとに目次アイテムを作成し、目次リストアイテムを一度にDOMに追加
最後にmapメソッドを使いますが、Arrayインスタンスのメソッドで、与えられた関数を配列のすべての要素に対して呼び出し、その結果からなる新しい配列を生成するので、Array.fromとしている。
目次リストアイテムを一度にDOMに追加することで、余分なDOM操作をせず、パフォーマンスを上げることができます。
// セクションタイトルごとに目次アイテムを作成
const tocItems = Array.from(sectionTitles).map((title, index) => createTOCItem(title, index));
// 目次リストアイテムを一度にDOMに追加
tocItems.forEach(item => tocList.appendChild(item));
全てのコード
// セレクタを変数に格納する
const container = document.querySelector('.container');
const sections = document.querySelectorAll('.sec');
const sectionTitles = document.querySelectorAll('.sec--ttl');
const tocList = document.querySelector('.toc--list');
// 目次リストアイテムを作成する関数を定義
function createTOCItem(title, index) {
title.setAttribute('id', `sec--ttl${index + 1}`);
const titleId = title.getAttribute('id');
const titleText = title.textContent;
// 目次リストアイテムを作成し、リンクを追加
const tocListItem = document.createElement('li');
const tocListLink = document.createElement('a');
tocListLink.textContent = titleText;
tocListItem.classList.add(`toc--list_item${index + 1}`, 'toc--list_item');
tocListItem.appendChild(tocListLink);
tocListLink.setAttribute('href', `#${titleId}`);
return tocListItem;
}
// セクションタイトルごとに目次アイテムを作成
const tocItems = Array.from(sectionTitles).map((title, index) => createTOCItem(title, index));
// 目次リストアイテムを一度にDOMに追加
tocItems.forEach(item => tocList.appendChild(item));
コメント