151 lines
No EOL
5.6 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|