stylescape
Version:
Stylescape is a visual identity framework developed by Scape Agency.
234 lines (195 loc) • 7.56 kB
text/typescript
// 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")
// }
// })
// }
// }