2021-10-11
JavaScript, WebsiteWithPython
現在の表示位置を目次内に表示する (WebsiteWithPython #3)
![]()
本ホームページの構成を紹介する. pythonで作る静的高速サイト というシリーズで執筆している.
テンプレートのcodesはGitHub: Website with Pythonで公開している. (もう少し改良したら本機能を追加したver.2をアップする予定)
#2の記事の公開以降も各種更新を続けており, 今回, 現在の表示位置をサイドバーにハイライト表示する機能を追加した. したがって, 今回はその機能について記す.
なお, 本機能の実装により, 本サイトはhtml, cssのみで構成されるサイトではなく, JavaScriptも使用したサイトとなった. html, cssのみで実装することへのこだわりで悩んだが,
- ハイライト機能はあった方が確実に利用しやすい
- 今回のJavaScriptの動作が軽い
ことから実装に至った.
目次
実際の挙動
実際の挙動はこの記事の右側のサイドバーを見ていただくか, もしくは下図の通り.
- PC

- Smartphone

実装
基本的には JSでのスクロール連動エフェクトにはIntersection Observerが便利 - ICS MEDIA の内容をほぼそのまま利用させていただき, 自分のサイト用に調整したのみ.
下記に簡易化したサンプルを掲載する. 以下の3ファイルを同一ディレクトリに入れ, index.htmlを開けば動作する. インストール等は不要.
index.html
xxxxxxxxxx<html lang="ja">
<head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width initial-scale=1'> <link rel="stylesheet" href="stylesheet.css"> <script defer type="text/javascript" src="toc-highlights.js"></script></head><body> <aside class="indexWrapper"> <h1>目次</h1> <ol id="indexList" class="index"> <li><a href="#index01">Lorem</a></li> <li><a href="#index02">Ipsum </a></li> <li><a href="#index03">Dolor</a></li> <li><a href="#index04">Sit amet</a></li> <li><a href="#index05">Consectetur</a></li> </ol> </aside> <main class="contents"> <div class="box" id="index01"> <h2>1</h2> <p>1111111aaaaaaaaaaaaaaaa</p> <p>1111111aaaaaaaaaaaaaaaa</p> <p>1111111aaaaaaaaaaaaaaaa</p> <p>1111111aaaaaaaaaaaaaaaa</p> <p>1111111aaaaaaaaaaaaaaaa</p> <p>1111111aaaaaaaaaaaaaaaa</p> <p>1111111aaaaaaaaaaaaaaaa</p> <p>1111111aaaaaaaaaaaaaaaa</p> <p>1111111aaaaaaaaaaaaaaaa</p> </div> <div class="box" id="index02"> <h2>2</h2> <p>222222aaaaaaaaaaaaaaaaa</p> <p>222222aaaaaaaaaaaaaaaaa</p> <p>222222aaaaaaaaaaaaaaaaa</p> <p>222222aaaaaaaaaaaaaaaaa</p> <p>222222aaaaaaaaaaaaaaaaa</p> <p>222222aaaaaaaaaaaaaaaaa</p> <p>222222aaaaaaaaaaaaaaaaa</p> </div> <div class="box" id="index03"> <h2>3</h2> <p>333333aaaaaaaaaaaaaaaaa</p> <p>333333aaaaaaaaaaaaaaaaa</p> <p>333333aaaaaaaaaaaaaaaaa</p> <p>333333aaaaaaaaaaaaaaaaa</p> <p>333333aaaaaaaaaaaaaaaaa</p> <p>333333aaaaaaaaaaaaaaaaa</p> </div> <div class="box" id="index04"> <h2>4</h2> <p>444444aaaaaaaaaaaaaaaaa</p> <p>444444aaaaaaaaaaaaaaaaa</p> <p>444444aaaaaaaaaaaaaaaaa</p> <p>444444aaaaaaaaaaaaaaaaa</p> <p>444444aaaaaaaaaaaaaaaaa</p> <p>444444aaaaaaaaaaaaaaaaa</p> </div> <div class="box" id="index05"> <h2>5</h2> <p>555555aaaaaaaaaaaaaaaaa</p> <p>555555aaaaaaaaaaaaaaaaa</p> <p>555555aaaaaaaaaaaaaaaaa</p> <p>555555aaaaaaaaaaaaaaaaa</p> <p>555555aaaaaaaaaaaaaaaaa</p> <p>555555aaaaaaaaaaaaaaaaa</p> <p>555555aaaaaaaaaaaaaaaaa</p> <p>555555aaaaaaaaaaaaaaaaa</p> </div> </main></body></html>
<script defer type="text/javascript" src="smooth-scroll.js"></script>のように, jsの読み込み指定をdeferにしている. asyncで実装していた時期もあったが, その際, たまにjsがうまく動作しないことがあった (jsが先に実行されてしまっていた?).
stylesheet.css
xxxxxxxxxx:root { scroll-behavior: smooth;}
.index a.active { background-color: #f3f3f3;}
.indexWrapper { position: fixed; right: 0;}
toc-highlights.js
xxxxxxxxxxconst boxes = document.querySelectorAll(".box");
const options = { root: null, rootMargin: "1% 0px -99% 0px", threshold: 0};const observer = new IntersectionObserver(doWhenIntersect, options);boxes.forEach(box => { observer.observe(box);});
function doWhenIntersect(entries) { entries.forEach(entry => { console.log(entry.target, entry.target.id, entry.isIntersecting); if (entry.isIntersecting) { activateIndex(entry.target); } });}
function activateIndex(element) { const currentActiveIndexes = document.querySelectorAll(".active"); if (currentActiveIndexes !== null) { currentActiveIndexes.forEach(currentActiveIndex => { currentActiveIndex.classList.remove("active"); }) }
const newActiveIndexes = document.querySelectorAll(`a[href='#${element.id}']`); newActiveIndexes.forEach(newActiveIndex => { newActiveIndex.classList.add("active"); });}
サンプルコードの挙動

注意
個人的な注意としては, html内の対象コンテンツはdivで取り囲んでやったほうが良いとわかった.
OK
xxxxxxxxxx:: <main class="contents"> <div class="box" id="index01"> <h2>1</h2> : : </div> <div class="box" id="index02"> <h2>2</h2> : : </div> <div class="box" id="index03"> <h2>3</h2> : : </div> <div class="box" id="index04"> <h2>4</h2> : : </div> <div class="box" id="index05"> <h2>5</h2> : : </div> </main>::
上記のOKの例のように各コンテンツをdivで取り囲み, 交差するコンテンツ同士が連続してつながるようにすると, 交差判定ミスが起きないため良い.
NG
xxxxxxxxxx:: <main class="contents"> <h2 class="box" id="index01">1</h2> : : <h2 class="box" id="index02">2</h2> : : <h2 class="box" id="index03">3</h2> : : <h2 class="box" id="index04">5</h2> : : <h2 class="box" id="index05">5</h2> : : </main>::
上記のNG例のように, 交差タイミングとなる箇所が断続的になってしまうと, 交差の判定が途切れてうまく動作せずに飛んでしまうことがあり, 不適.
参照
その他
- JavaScript を始める準備 - こくぶん研究室
- デバッグに大活躍! JavaScriptのconsole.logで値を表示しよう | 侍エンジニアブログ
- スクロールに連動して現在地を示す目次を作りたい - Qiita
- 今すぐ覚えられる!HTMLでJavaScriptを読み込む(呼び出す)方法を現役エンジニアが解説【初心者向け】 | TechAcademyマガジン
- JavaScriptとは?基本的な書き方や勉強方法~入門~| IT・エンジニア派遣のModis
- JavaScript の書き方 - とほほのWWW入門
- JavaScript実行のタイミングと仕組み - 第1章 JavaScript言語仕様 - [SMART]
- JavaScript | loadイベント:ページの読み込みが完了したとき