aboutsummaryrefslogtreecommitdiff
path: root/themes/hugo-theme-stack/assets/ts/scrollspy.ts
diff options
context:
space:
mode:
Diffstat (limited to 'themes/hugo-theme-stack/assets/ts/scrollspy.ts')
-rw-r--r--themes/hugo-theme-stack/assets/ts/scrollspy.ts131
1 files changed, 0 insertions, 131 deletions
diff --git a/themes/hugo-theme-stack/assets/ts/scrollspy.ts b/themes/hugo-theme-stack/assets/ts/scrollspy.ts
deleted file mode 100644
index 8a14085..0000000
--- a/themes/hugo-theme-stack/assets/ts/scrollspy.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-// Implements a scroll spy system for the ToC, displaying the current section with an indicator and scrolling to it when needed.
-
-// Inspired from https://gomakethings.com/debouncing-your-javascript-events/
-function debounced(func: Function) {
- let timeout;
- return () => {
- if (timeout) {
- window.cancelAnimationFrame(timeout);
- }
-
- timeout = window.requestAnimationFrame(() => func());
- }
-}
-
-const headersQuery = ".article-content h1[id], .article-content h2[id], .article-content h3[id], .article-content h4[id], .article-content h5[id], .article-content h6[id]";
-const tocQuery = "#TableOfContents";
-const navigationQuery = "#TableOfContents li";
-const activeClass = "active-class";
-
-function scrollToTocElement(tocElement: HTMLElement, scrollableNavigation: HTMLElement) {
- let textHeight = tocElement.querySelector("a").offsetHeight;
- let scrollTop = tocElement.offsetTop - scrollableNavigation.offsetHeight / 2 + textHeight / 2 - scrollableNavigation.offsetTop;
- if (scrollTop < 0) {
- scrollTop = 0;
- }
- scrollableNavigation.scrollTo({ top: scrollTop, behavior: "smooth" });
-}
-
-type IdToElementMap = { [key: string]: HTMLElement };
-
-function buildIdToNavigationElementMap(navigation: NodeListOf<Element>): IdToElementMap {
- const sectionLinkRef: IdToElementMap = {};
- navigation.forEach((navigationElement: HTMLElement) => {
- const link = navigationElement.querySelector("a");
- const href = link.getAttribute("href");
- if (href.startsWith("#")) {
- sectionLinkRef[href.slice(1)] = navigationElement;
- }
- });
-
- return sectionLinkRef;
-}
-
-function computeOffsets(headers: NodeListOf<Element>) {
- let sectionsOffsets = [];
- headers.forEach((header: HTMLElement) => { sectionsOffsets.push({ id: header.id, offset: header.offsetTop }) });
- sectionsOffsets.sort((a, b) => a.offset - b.offset);
- return sectionsOffsets;
-}
-
-function setupScrollspy() {
- let headers = document.querySelectorAll(headersQuery);
- if (!headers) {
- console.warn("No header matched query", headers);
- return;
- }
-
- let scrollableNavigation = document.querySelector(tocQuery) as HTMLElement | undefined;
- if (!scrollableNavigation) {
- console.warn("No toc matched query", tocQuery);
- return;
- }
-
- let navigation = document.querySelectorAll(navigationQuery);
- if (!navigation) {
- console.warn("No navigation matched query", navigationQuery);
- return;
- }
-
- let sectionsOffsets = computeOffsets(headers);
-
- // We need to avoid scrolling when the user is actively interacting with the ToC. Otherwise, if the user clicks on a link in the ToC,
- // we would scroll their view, which is not optimal usability-wise.
- let tocHovered: boolean = false;
- scrollableNavigation.addEventListener("mouseenter", debounced(() => tocHovered = true));
- scrollableNavigation.addEventListener("mouseleave", debounced(() => tocHovered = false));
-
- let activeSectionLink: Element;
-
- let idToNavigationElement: IdToElementMap = buildIdToNavigationElementMap(navigation);
-
- function scrollHandler() {
- let scrollPosition = document.documentElement.scrollTop || document.body.scrollTop;
-
- let newActiveSection: HTMLElement | undefined;
-
- // Find the section that is currently active.
- // It is possible for no section to be active, so newActiveSection may be undefined.
- sectionsOffsets.forEach((section) => {
- if (scrollPosition >= section.offset - 20) {
- newActiveSection = document.getElementById(section.id);
- }
- });
-
- // Find the link for the active section. Once again, there are a few edge cases:
- // - No active section = no link => undefined
- // - No active section but the link does not exist in toc (e.g. because it is outside of the applicable ToC levels) => undefined
- let newActiveSectionLink: HTMLElement | undefined
- if (newActiveSection) {
- newActiveSectionLink = idToNavigationElement[newActiveSection.id];
- }
-
- if (newActiveSection && !newActiveSectionLink) {
- // The active section does not have a link in the ToC, so we can't scroll to it.
- console.debug("No link found for section", newActiveSection);
- } else if (newActiveSectionLink !== activeSectionLink) {
- if (activeSectionLink)
- activeSectionLink.classList.remove(activeClass);
- if (newActiveSectionLink) {
- newActiveSectionLink.classList.add(activeClass);
- if (!tocHovered) {
- // Scroll so that newActiveSectionLink is in the middle of scrollableNavigation, except when it's from a manual click (hence the tocHovered check)
- scrollToTocElement(newActiveSectionLink, scrollableNavigation);
- }
- }
- activeSectionLink = newActiveSectionLink;
- }
- }
-
- window.addEventListener("scroll", debounced(scrollHandler));
-
- // Resizing may cause the offset values to change: recompute them.
- function resizeHandler() {
- sectionsOffsets = computeOffsets(headers);
- scrollHandler();
- }
-
- window.addEventListener("resize", debounced(resizeHandler));
-}
-
-export { setupScrollspy }; \ No newline at end of file