UNPKG

stylescape

Version:

Stylescape is a visual identity framework developed by Scape Agency.

234 lines (195 loc) 7.56 kB
// Updates navigation based on scroll position, commonly used in single-page websites. export class ScrollSpyManager { private sections: HTMLElement[] private navLinks: NodeListOf<HTMLElement> private scrollContainer: HTMLElement | Window private thresholdOffset: number private ticking = false constructor( sections: HTMLElement[], navLinksSelector: string, containerId?: string, thresholdOffset: number = 0.5, ) { this.sections = sections this.navLinks = document.querySelectorAll(navLinksSelector) this.scrollContainer = containerId ? (document.getElementById(containerId) ?? window) : window this.thresholdOffset = thresholdOffset this.bindScrollListener() this.updateActiveLink() } private bindScrollListener(): void { const container = this.scrollContainer === window ? window : this.scrollContainer container.addEventListener("scroll", () => this.onScroll(), { passive: true, }) } private onScroll(): void { if (!this.ticking) { window.requestAnimationFrame(() => { this.updateActiveLink() this.ticking = false }) this.ticking = true } } private updateActiveLink(): void { if (!this.sections || this.sections.length === 0 || !this.navLinks) return const scrollY = this.scrollContainer instanceof Window ? window.scrollY : this.scrollContainer.scrollTop let activeId: string | null = null for (const section of this.sections) { const id = section.getAttribute("id") if (!id) continue const top = section.offsetTop const height = section.offsetHeight const threshold = top - height * this.thresholdOffset if (scrollY >= threshold) { activeId = id } } this.navLinks.forEach((link) => { const targetId = link.getAttribute("href")?.replace("#", "") const isActive = targetId === activeId link.classList.toggle("active", isActive) let parent = link.parentElement while (parent && parent !== document.body) { if (parent.tagName === "LI") { parent.classList.remove("active") } parent = parent.parentElement } if (isActive) { let parent = link.parentElement while (parent && parent !== document.body) { if (parent.tagName === "LI") { parent.classList.add("active") } parent = parent.parentElement } } }) } } // Updates navigation based on scroll position, commonly used in single-page websites. // ScrollSpyManager: Activates nav links based on scroll position. // export class ScrollSpyManager { // private sections: HTMLElement[] // private navLinks: NodeListOf<HTMLElement> // private scrollContainer: HTMLElement | Window // private thresholdOffset: number // private ticking = false // constructor( // sections: HTMLElement[], // navLinksSelector: string, // containerId?: string, // thresholdOffset: number = 0.5, // default halfway through section // ) { // this.sections = sections // this.navLinks = document.querySelectorAll(navLinksSelector) // this.scrollContainer = containerId // ? (document.getElementById(containerId) ?? window) // : window // this.thresholdOffset = thresholdOffset // this.bindScrollListener() // this.updateActiveLink() // } // private bindScrollListener(): void { // const container = // this.scrollContainer === window ? window : this.scrollContainer // container.addEventListener("scroll", () => this.onScroll(), { // passive: true, // }) // } // private onScroll(): void { // if (!this.ticking) { // window.requestAnimationFrame(() => { // this.updateActiveLink() // this.ticking = false // }) // this.ticking = true // } // } // private updateActiveLink(): void { // const scrollY = // this.scrollContainer instanceof Window // ? window.scrollY // : this.scrollContainer.scrollTop // let activeId: string | null = null // for (const section of this.sections) { // const id = section.getAttribute("id") // if (!id) continue // const top = section.offsetTop // const height = section.offsetHeight // const threshold = top - height * this.thresholdOffset // if (scrollY >= threshold) { // activeId = id // } // } // this.navLinks.forEach((link) => { // const targetId = link.getAttribute("href")?.replace("#", "") // link.classList.toggle("active", targetId === activeId) // }) // } // } // export class ScrollSpyManager { // private sections: HTMLElement[] // private navLinks: NodeListOf<HTMLElement> // private scrollContainer: HTMLElement | Window // constructor( // sections: HTMLElement[], // navLinksSelector: string, // containerId?: string, // ) { // this.sections = sections // this.navLinks = document.querySelectorAll(navLinksSelector) // this.scrollContainer = containerId // ? document.getElementById(containerId) || window // : window // this.attachScrollListener() // } // private attachScrollListener(): void { // // console.log('attachScrollListener'); // const scrollHandler = // this.scrollContainer === window // ? window.addEventListener( // "scroll", // this.updateActiveLink.bind(this), // ) // : this.scrollContainer.addEventListener( // "scroll", // this.updateActiveLink.bind(this), // true, // ) // this.updateActiveLink() // Initialize active state // } // private updateActiveLink(): void { // // console.log('updateActiveLink'); // // console.log(this.sections); // // console.log(this.navLinks); // let currentSection = "" // const containerScrollY = // this.scrollContainer instanceof Window // ? window.scrollY // : this.scrollContainer.scrollTop // this.sections.forEach((section) => { // const sectionTop = section.offsetTop // const sectionHeight = section.clientHeight // if (containerScrollY >= sectionTop - sectionHeight / 2) { // currentSection = section.getAttribute("id")! // } // }) // this.navLinks.forEach((link) => { // link.classList.remove("active") // if (link.getAttribute("href") === `#${currentSection}`) { // link.classList.add("active") // } // }) // } // }