Files
brk/website_next/learn/scroll-spy.js
T
2026-06-05 18:12:46 +02:00

64 lines
1.5 KiB
JavaScript

/** @param {HTMLElement} main */
export function initScrollSpy(main) {
const headings = [...main.querySelectorAll("article h1, article h2")];
const visibleHeadings = new Set();
const links = new Map(
[...main.querySelectorAll('nav a[href^="#"]')].map((link) => [
link.getAttribute("href"),
link,
]),
);
/** @type {string | null} */
let current = null;
/** @param {Element} heading */
function getHash(heading) {
const section = /** @type {HTMLElement} */ (
heading.closest("section[id]")
);
return `#${section.id}`;
}
/** @param {string} hash */
function getLink(hash) {
return /** @type {HTMLAnchorElement} */ (links.get(hash));
}
/** @param {string} hash */
function setCurrent(hash) {
if (hash === current) return;
if (current) getLink(current).removeAttribute("aria-current");
getLink(hash).setAttribute("aria-current", "location");
history.replaceState(null, "", hash);
current = hash;
}
function update() {
if (main.hidden) return;
const heading = headings.findLast((heading) =>
visibleHeadings.has(heading),
);
if (heading) setCurrent(getHash(heading));
}
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
visibleHeadings.add(entry.target);
} else {
visibleHeadings.delete(entry.target);
}
}
update();
},
{ rootMargin: "0px 0px -80% 0px" },
);
for (const heading of headings) observer.observe(heading);
}