karsttech.com/assets/js/toc-highlight.js

151 lines
No EOL
5.6 KiB
JavaScript

document.addEventListener('DOMContentLoaded', function() {
const tocLinks = document.querySelectorAll('#TableOfContents a');
const tocContainer = document.querySelector('.toc');
const sections = new Map(); // Store section elements with their corresponding TOC links
let lastActiveSection = null;
let scrollTimeout = null;
let isScrolling = false;
// Build a map of section IDs to their TOC links
tocLinks.forEach(link => {
const sectionId = decodeURIComponent(link.getAttribute('href').substring(1));
const section = document.getElementById(sectionId);
if (section) {
sections.set(section, link);
}
});
// Function to scroll TOC container to keep active item in view
const scrollTocToActive = (activeLink) => {
if (!tocContainer || !activeLink) return;
const containerRect = tocContainer.getBoundingClientRect();
const linkRect = activeLink.getBoundingClientRect();
// Check if the active link is outside the visible area
if (linkRect.top < containerRect.top || linkRect.bottom > containerRect.bottom) {
// Calculate the scroll position to center the active link
const scrollTop = activeLink.offsetTop - (containerRect.height / 2) + (linkRect.height / 2);
// Use CSS smooth scrolling if available, otherwise use JS
if ('scrollBehavior' in document.documentElement.style) {
tocContainer.scrollTo({
top: scrollTop,
behavior: 'smooth'
});
} else {
// Fallback for browsers that don't support smooth scrolling
tocContainer.scrollTop = scrollTop;
}
}
};
// Function to find the section closest to the center of the viewport
const findClosestSectionToCenter = () => {
let closestSection = null;
let closestDistance = Infinity;
const viewportHeight = window.innerHeight;
const viewportCenter = viewportHeight / 2;
sections.forEach((link, section) => {
const rect = section.getBoundingClientRect();
// Calculate the center of the section
const sectionCenter = rect.top + (rect.height / 2);
// Calculate distance from the center of the viewport
const distance = Math.abs(sectionCenter - viewportCenter);
// If this section is closer to the center than our current closest
if (distance < closestDistance) {
closestDistance = distance;
closestSection = section;
}
});
return closestSection;
};
// Function to update the active section
const updateActiveSection = (activeSection) => {
if (!activeSection) return;
// Update active state
tocLinks.forEach(link => link.classList.remove('active'));
const activeLink = sections.get(activeSection);
if (activeLink) {
activeLink.classList.add('active');
lastActiveSection = activeSection;
// Use a small delay before scrolling to prevent rapid changes
if (scrollTimeout) {
clearTimeout(scrollTimeout);
}
scrollTimeout = setTimeout(() => {
scrollTocToActive(activeLink);
}, 100);
}
};
// Function to handle scroll events
const handleScroll = () => {
// Clear any pending scroll timeout
if (scrollTimeout) {
clearTimeout(scrollTimeout);
}
// Find the section closest to the center of the viewport
const closestSection = findClosestSectionToCenter();
// Update the active section
updateActiveSection(closestSection);
};
// Add scroll event listener with debouncing
let scrollDebounceTimer = null;
window.addEventListener('scroll', () => {
isScrolling = true;
// Debounce the scroll event
if (scrollDebounceTimer) {
clearTimeout(scrollDebounceTimer);
}
scrollDebounceTimer = setTimeout(() => {
isScrolling = false;
handleScroll();
}, 100);
});
// Initial check to ensure a section is highlighted
setTimeout(() => {
handleScroll();
}, 500);
// Smooth scroll to section when clicking TOC links
tocLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const targetId = decodeURIComponent(link.getAttribute('href').substring(1));
const targetSection = document.getElementById(targetId);
if (targetSection) {
// Use our custom smooth scrolling function if available
if (window.smoothScrollTo) {
window.smoothScrollTo(targetSection);
} else {
// Fallback to standard smooth scrolling
targetSection.scrollIntoView({ behavior: 'smooth' });
}
// Also update the active section immediately
const activeLink = sections.get(targetSection);
if (activeLink) {
tocLinks.forEach(l => l.classList.remove('active'));
activeLink.classList.add('active');
lastActiveSection = targetSection;
scrollTocToActive(activeLink);
}
}
});
});
});