Before Everything
The static site generator Hugo I used in for this site auto-generates TOC for each article. The rendered TOC html DOM structure is like:
<aside class="sidebar right-sidebar">
<header>
<h2>
<i class="fas fa-list"></i>
<span>CONENTS</span>
</h2>
</header>
<nav id="TableOfContents">
<ul>
<!--...-->
<li><a href="#data">Heading 1</a>
<ul>
<li><a href="#realm">Heading 1.1</a></li>
<li><a href="#realm">Heading 1.2</a></li>
<!--...-->
</ul>
</li>
<!--...-->
</ul>
</nav>
</aside>
It basically scans the article body part for headings (h1 h2 h3 h4 ...
), each
of which was rendered with an id
attribute. For each heading Hugo adds a li
item in TOC at appropriate level.
Steps
Scan the TOC collecting all anchors like
#realm
in<a href="realm">
.
For an anchor like#realm
there must be only one instance of element with"realm"
as itsid
attribute.Scan for
$('[id=' + anchorName + ']')
part for heading elements corresponding to items listed in TOC.Collect their top x coordinate using
$(...).offset.top
.
Make a mapping like{ anchorName: headingOffset }
.Add an event handler
$(window).scroll(function() { ... })
.
When window scrolls, get thewindow.pageYOffset
and traverse the mapping collected above to found the closest heading that is beyond the window viewport offset.Highlight the corresponding
<li>
element in TOC by ‘$(…).addClass(‘current’)‘.
Full code
$(document).ready(function() {
$(window).scroll(function() {
$("#TableOfContents a").removeClass("current")
currentAnchor().addClass("current")
})
});
function tocItem(anchor) {
return $("[href=\"" + anchor + "\"]")
}
function heading(anchor) {
return $("[id=" + anchor.substr(1) + "]")
}
var _anchors = null
function anchors() {
if (!_anchors) {
_anchors = $("#TableOfContents a").map(function() {
return $(this).attr("href")
})
}
return _anchors
}
function currentAnchor() {
var winY = window.pageYOffset
var currAnchor = null
anchors().each(function() {
var y = heading(this).position().top
if (y < winY + window.innerHeight * 0.23) {
currAnchor = this
return
}
})
return tocItem(currAnchor)
}