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();