const toc = document.querySelector(".toc");
const tocPath = toc.querySelector("svg.toc-marker path");
const anchors = toc.querySelectorAll("a");
const tocItems = Array.from(anchors)
.map((anchor) => {
const target = document.getElementById(
anchor.getAttribute("href").slice(1)
);
return {
anchor, // ref to
target, // ref to 's target
pathStart: undefined,
pathEnd: undefined
};
})
.filter((tocItem) => !!tocItem.target); // Remove missing targets
let pathLength;
//
//
//
function drawPath() {
//
// Compute path=""
//
const path = [];
let pathIndent;
tocItems.forEach((tocItem, i) => {
const x = tocItem.anchor.offsetLeft - 5;
const y = tocItem.anchor.offsetTop;
const height = tocItem.anchor.offsetHeight;
if (i === 0) {
path.push("M", x, y, "L", x, y + height);
tocItem.pathStart = 0;
} else {
// Draw an additional line when there's a change in indent levels
if (pathIndent !== x) path.push("L", pathIndent, y);
path.push("L", x, y);
// Set the current path so that we can measure it
tocPath.setAttribute("d", path.join(" "));
tocItem.pathStart = tocPath.getTotalLength() || 0;
path.push("L", x, y + height);
}
pathIndent = x;
tocPath.setAttribute("d", path.join(" "));
tocItem.pathEnd = tocPath.getTotalLength();
});
pathLength = tocPath.getTotalLength();
sync();
}
//
//
//
let lastPathStart;
let lastPathEnd;
// Factor of screen size that the element must cross
// before it's considered visible
const TOP_MARGIN = 0.1;
const BOTTOM_MARGIN = 0.2;
function sync() {
const windowHeight = window.innerHeight;
let pathStart = pathLength;
let pathEnd = 0;
let visibleItems = 0;
tocItems.forEach((tocItem) => {
const targetBounds = tocItem.target.getBoundingClientRect();
if (
targetBounds.bottom > windowHeight * TOP_MARGIN &&
targetBounds.top < windowHeight * (1 - BOTTOM_MARGIN)
) {
pathStart = Math.min(tocItem.pathStart, pathStart);
pathEnd = Math.max(tocItem.pathEnd, pathEnd);
visibleItems += 1;
tocItem.anchor.classList.add("visible");
} else {
tocItem.anchor.classList.remove("visible");
}
});
// Specify the visible path or hide the path altogether
// if there are no visible items
if (visibleItems > 0 && pathStart < pathEnd) {
if (pathStart !== lastPathStart || pathEnd !== lastPathEnd) {
tocPath.setAttribute("stroke-dashoffset", "1");
tocPath.setAttribute(
"stroke-dasharray",
`1,${pathStart},${pathEnd - pathStart},${pathLength}`
);
tocPath.setAttribute("opacity", 1);
}
} else {
tocPath.setAttribute("opacity", 0);
}
lastPathStart = pathStart;
lastPathEnd = pathEnd;
}
window.addEventListener("resize", drawPath, false);
window.addEventListener("scroll", sync, false);
drawPath();