【JavaScript】見出しまでスクロールできる目次の作り方
ブログに目次を設置したかったので作成した。
サンプル
前提
下記の関数やオブジェクトを定義済みの想定とする。
- 【JavaScript】要素を押した時のイベントを登録する関数( プロトタイプ拡張 )
- 【JavaScript】要素をホバーした時のイベントを登録する関数( プロトタイプ拡張 )
- 【JavaScript】DOMを操作する時によく使う関数の一覧( プロトタイプ拡張 )
- 【JavaScript】要素をスクロールするオブジェクト
手順
目次を作成するオブジェクトを定義する
目次を作成するため、下記の2つのオブジェクトを定義する。
function IndexCreator() {
function _create() {
const INDEX = document.getElementById( 'index' );
if ( INDEX === null ) return;
const CONTENT = document.querySelector( '.content' );
if ( CONTENT === null ) return;
const HEADING_SELECTORS = '.content > h2, .content > h3, .content > h4';
const HEADINGS = CONTENT.querySelectorAll( HEADING_SELECTORS );
if ( HEADINGS.length === 0 ) return;
_createTitle( INDEX );
const DIV = 'div'.createElementAndAddClass( 'items' );
const ITEMS = _createItems( HEADINGS, DIV );
INDEX.appendChild( DIV );
const REGISTERER = new IndexEventRegisterer;
REGISTERER.register( ITEMS, HEADINGS );
}
function _createTitle( index ) {
index.insertAdjacentHTML( 'beforeend', '<h3 class="title">目次</h3>' );
}
function _createItems( headings, beforeElement ) {
const getNestCount = function( heading ) {
let count = 1;
const NODE_NAME = heading.nodeName.toLowerCase();
switch( NODE_NAME ) {
case 'h3': count = 2; break;
case 'h4': count = 3; break;
}
return count;
};
const addNest = function() {
const OL = 'ol'.createElement();
beforeElement = beforeElement.appendChild( OL );
};
const removeNest = function() {
const OL = beforeElement.parentNode;
beforeElement = OL.parentNode;
};
let currentNestCount = 0;
const adjustNest = function( heading ) {
const NEST_COUNT = getNestCount( heading );
if ( currentNestCount === NEST_COUNT ) return false;
const IS_NEST = currentNestCount < NEST_COUNT;
if ( IS_NEST ) {
addNest();
}
else {
const REMOVING_COUNT = currentNestCount - NEST_COUNT;
for ( let i = 0; i < REMOVING_COUNT; i++ ) {
removeNest();
}
}
currentNestCount = NEST_COUNT;
return IS_NEST;
};
let items = [];
const createItem = function( heading, index ) {
const IS_NESTED = adjustNest( heading );
const OL = IS_NESTED ? beforeElement : beforeElement.parentNode;
const LI = 'li' .createElement();
const ITEM = 'div' .createElementAndAddClass( 'item' );
const LINE = 'div' .createElementAndAddClass( 'line' );
const NUMBER = 'div' .createElementAndAddClass( 'number' );
const TEXT = 'div' .createElementAndAddClass( 'text' );
beforeElement = OL.appendChild( LI );
LI .appendChild( ITEM );
ITEM.appendChild( LINE );
LINE.appendChild( NUMBER );
LINE.appendChild( TEXT );
ITEM.style.paddingLeft = ( currentNestCount * 10 ) + 'px';
TEXT.textContent = heading.textContent;
items.push( ITEM );
};
const COUNT = headings.length;
for ( let i = 0; i < COUNT; i++ ) createItem( headings[i], i );
return items;
}
return {
create: _create,
}
};
function IndexEventRegisterer() {
const _SCROLLER = new Scroller( document.documentElement, 70, 15 );
function _register( items, headings ) {
const COUNT = headings.length;
for ( let i = 0; i < COUNT; i++ ) {
const ITEM = items[i];
const HEADING = headings[i];
_registerHoverEvent ( ITEM, i );
_registerPushedEvent( ITEM, HEADING );
}
}
function _registerHoverEvent( item, index ) {
const _CLASS_NAME = 'hover';
const onStarted = function() { item.addClass( _CLASS_NAME ); };
const onEnded = function() { item.removeClass( _CLASS_NAME ); };
item.registerOnHover( onStarted, onEnded );
}
function _registerPushedEvent( item, heading ) {
const onPushed = function() { _SCROLLER.scrollByElement( heading ); };
item.registerOnPushed( onPushed );
}
return {
register: _register,
};
};
目次を設置する箇所にdiv要素を置く
ページ内の目次を設置する箇所に<div id="index"></div>
を置く。
DOMContentLoadedイベントでオブジェクトを生成し、関数を呼ぶ
IndexCreator
オブジェクトを生成し、create
関数を呼ぶ処理をdocument
オブジェクトのDOMContentLoaded
イベントに登録する。
document.addEventListener( 'DOMContentLoaded', function() {
const INDEX_CREATOR = new IndexCreator;
INDEX_CREATOR.create();
} );
ページ内のコンテンツをdivタグで囲む
ページ内のコンテンツを<div class="content"></div>
で囲む。
コンテンツ内に見出しを配置する
コンテンツ内にh2~h4
のタグを使い、見出しを配置する。
<div class="content">
<h2>見出し1</h2>
<p>文章</p>
<h3>見出し1.1</h3>
<p>文章</p>
<h2>見出し2</h2>
<p>文章</p>
</div>
これでページを表示した時に目次を設置する箇所に下記のようなHTMLが出力される。
<div id="index">
<h3 class="title">目次</h3>
<div class="items">
<ol>
<li>
<div class="item" style="padding-left: 10px;">
<div class="line">
<div class="number"></div><div class="text">見出し1</div>
</div>
</div>
<ol>
<li>
<div class="item" style="padding-left: 20px;">
<div class="line">
<div class="number"></div><div class="text">見出し1.1</div>
</div>
</div>
</li>
</ol>
</li>
<li>
<div class="item" style="padding-left: 10px;">
<div class="line">
<div class="number"></div><div class="text">見出し2</div>
</div>
</div>
</li>
</ol>
</div>
</div>
CSSで見た目を整える
後は出力されたHTMLの各要素に付いているクラスを用いて、CSSで見た目を整える。