111 lines
3.2 KiB
JavaScript
111 lines
3.2 KiB
JavaScript
// Section tracking and navigation highlighting
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
const sections = document.querySelectorAll("section[id]");
|
|
const navLinks = document.querySelectorAll(".nav-links a");
|
|
|
|
// Configuration
|
|
const offset = 100; // Offset for when section becomes "active" (accounts for fixed nav)
|
|
let isScrolling = false;
|
|
|
|
function getCurrentSection() {
|
|
let currentSection = "";
|
|
const scrollPosition = window.scrollY + offset;
|
|
|
|
// Find which section we're currently in
|
|
sections.forEach((section) => {
|
|
const sectionTop = section.offsetTop;
|
|
const sectionHeight = section.offsetHeight;
|
|
const sectionId = section.getAttribute("id");
|
|
|
|
if (
|
|
scrollPosition >= sectionTop &&
|
|
scrollPosition < sectionTop + sectionHeight
|
|
) {
|
|
currentSection = sectionId;
|
|
}
|
|
});
|
|
|
|
// Special case: if we're at the very top, activate the first section
|
|
if (window.scrollY < 100) {
|
|
currentSection = sections[0]?.getAttribute("id") || "";
|
|
}
|
|
|
|
return currentSection;
|
|
}
|
|
|
|
function updateActiveNavLink() {
|
|
const currentSection = getCurrentSection();
|
|
|
|
navLinks.forEach((link) => {
|
|
const href = link.getAttribute("href");
|
|
|
|
// Remove active class from all links
|
|
link.classList.remove("active");
|
|
|
|
// Add active class to matching link
|
|
if (href === `#${currentSection}`) {
|
|
link.classList.add("active");
|
|
}
|
|
});
|
|
}
|
|
|
|
// Throttle scroll events for better performance
|
|
function handleScroll() {
|
|
if (!isScrolling) {
|
|
window.requestAnimationFrame(() => {
|
|
updateActiveNavLink();
|
|
isScrolling = false;
|
|
});
|
|
isScrolling = true;
|
|
}
|
|
}
|
|
|
|
// Listen for scroll events
|
|
window.addEventListener("scroll", handleScroll);
|
|
|
|
// Update on page load
|
|
updateActiveNavLink();
|
|
|
|
// Also update when clicking nav links (for smooth scroll)
|
|
navLinks.forEach((link) => {
|
|
link.addEventListener("click", function (e) {
|
|
// Remove active from all
|
|
navLinks.forEach((l) => l.classList.remove("active"));
|
|
// Add active to clicked link
|
|
this.classList.add("active");
|
|
|
|
// Let the scroll handler update it properly after scroll completes
|
|
setTimeout(updateActiveNavLink, 100);
|
|
});
|
|
});
|
|
|
|
// Handle hash changes (browser back/forward)
|
|
window.addEventListener("hashchange", function () {
|
|
setTimeout(updateActiveNavLink, 100);
|
|
});
|
|
|
|
// Intersection Observer for more precise tracking (progressive enhancement)
|
|
if ("IntersectionObserver" in window) {
|
|
const observerOptions = {
|
|
rootMargin: "-20% 0px -70% 0px", // Trigger when section is roughly in the middle of viewport
|
|
threshold: 0,
|
|
};
|
|
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach((entry) => {
|
|
if (entry.isIntersecting) {
|
|
const sectionId = entry.target.getAttribute("id");
|
|
navLinks.forEach((link) => {
|
|
link.classList.remove("active");
|
|
if (link.getAttribute("href") === `#${sectionId}`) {
|
|
link.classList.add("active");
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}, observerOptions);
|
|
|
|
// Observe all sections
|
|
sections.forEach((section) => observer.observe(section));
|
|
}
|
|
});
|