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
xxxxxxxxxx
const 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イベント:ページの読み込みが完了したとき