From b6991a56e71c4d7534233b718b074740702db12b Mon Sep 17 00:00:00 2001 From: MoritzWeber Date: Tue, 9 Sep 2025 20:59:24 +0200 Subject: [PATCH] fix: Change highlight logic from intersector to scroll detection --- assets/js/highlightHeadline.js | 90 ++++++++++++---------------------- 1 file changed, 32 insertions(+), 58 deletions(-) diff --git a/assets/js/highlightHeadline.js b/assets/js/highlightHeadline.js index c71efc63..40e195fb 100644 --- a/assets/js/highlightHeadline.js +++ b/assets/js/highlightHeadline.js @@ -15,74 +15,48 @@ function initHighlightHeadline() { const markTocItemInactive = (a) => {return a.removeAttribute('data-current');}; const getTocLinkFromHeading = (h) => {return document.querySelector(`.o-aside__toc a[href="${windowPath}#${encodeURIComponent(h.id).replace(/%\w\w/g, match => match.toLowerCase())}"]`);} - const getDocHeight = () => Math.floor(document.body.clientHeight); + let scrollDebounce; - const visibleHeadings = new Set(); - let resizeDebounce; - let currentObserver; - let height = getDocHeight(); - - function beginObservation(docHeight) { - const observer = new IntersectionObserver( - (entries) => { - if (!isAsideActive()) { - return; - } + function updateActiveHeading() { + if (!isAsideActive()) { + return; + } - entries.forEach((entry) => { - if (entry.isIntersecting) { - visibleHeadings.add(entry.target); - } else { - visibleHeadings.delete(entry.target); - } - }); + let currentHeading = null; - // Sort visible (intersecting) headings by inverted order of appearance, then grab the first item (i.e. last visible heading) - const lastVisible = Array.from(visibleHeadings.values()).sort((a, b) => headings.indexOf(b) - headings.indexOf(a))[0]; - if (!lastVisible) { - return; - } + for (let i = 0; i < headings.length; i++) { + const heading = headings[i]; - headings.forEach((heading) => { - // If it's the last visible item, mark it to make it stand out, else, revert to the default style - // Find the link in the TOC list matching the heading in this list of heading elements - const tocLink = getTocLinkFromHeading(heading); - if (heading === lastVisible) { - if(tocLink){ - markTocItemActive(tocLink); - } - } else { - if(tocLink){ - markTocItemInactive(tocLink); - } - } - }); - }, - { - rootMargin: `${docHeight}px 0px -90% 0px`, - threshold: 1, // Only considered intersecting if all the pixels are inside the intersection area + // Add the scroll-padding-top defined in styles.scss + 1px to trigger the detection + if (heading.offsetTop <= window.pageYOffset + 81) { + currentHeading = heading; + } else { + break; } - ); - - headings.forEach((heading) => observer.observe(heading)); + } - return observer; + headings.forEach((heading) => { + const tocLink = getTocLinkFromHeading(heading); + if (tocLink) { + if (heading === currentHeading) { + markTocItemActive(tocLink); + } else { + markTocItemInactive(tocLink); + } + } + }); } - currentObserver = beginObservation(height); // Start the intersection observer + updateActiveHeading(); + + window.addEventListener('scroll', () => { + clearTimeout(scrollDebounce); + scrollDebounce = setTimeout(updateActiveHeading, 5); + }); - // On resize, replace the observer with a new one matching the updated body height, if different window.addEventListener('resize', () => { - clearTimeout(resizeDebounce); - resizeDebounce = setTimeout(() => { - const heightAfterResize = getDocHeight(); - if (height !== heightAfterResize) { - if (currentObserver) { - currentObserver.disconnect(); - } - currentObserver = beginObservation(heightAfterResize); - } - }, 200); + clearTimeout(scrollDebounce); + scrollDebounce = setTimeout(updateActiveHeading, 5); }); }