Created
May 10, 2016 16:15
-
-
Save danharper/f456b2bb718346bae50796ca4d3d6eca to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export type Options = { | |
blockSelector: () => string, | |
linkSelector: (id: string) => string, | |
classToToggle?: () => string, | |
toggleClass?: (el: Element, isInView: boolean, classToToggle: string) => void, | |
} | |
const defaults: Options = { | |
blockSelector: () => '', | |
linkSelector: () => '', | |
classToToggle: () => 'active', | |
toggleClass: (el: Element, isInView: boolean, classToToggle: string) => { | |
if (isInView) { | |
el.classList.add(classToToggle) | |
} | |
else { | |
el.classList.remove(classToToggle) | |
} | |
} | |
} | |
type Bounds = { top: number, bottom: number } | |
export default class ScrollSpy { | |
private options: Options | |
private elements: Array<Bounds & { link: Element }> | |
constructor(options: Options) { | |
this.options = Object.assign({}, defaults, options) | |
document.addEventListener('DOMContentLoaded', () => { | |
this.syncElementPositions() | |
this.bootWindowListeners() | |
this.highlightElementsInViewport() | |
}) | |
} | |
private bootWindowListeners() { | |
window.addEventListener('resize', () => { | |
this.syncElementPositions() | |
this.highlightElementsInViewport() | |
}) | |
window.addEventListener('scroll', this.highlightElementsInViewport.bind(this)) | |
} | |
private syncElementPositions() { | |
this.elements = this.select(this.options.blockSelector()).map((el: Element) => { | |
return { | |
link: document.querySelector(this.options.linkSelector(el.id)), | |
top: el.getBoundingClientRect().top, | |
bottom: el.getBoundingClientRect().top + el.getBoundingClientRect().height, | |
} | |
}) | |
} | |
private highlightElementsInViewport() { | |
const viewport: Bounds = { | |
top: window.scrollY, | |
bottom: window.scrollY + window.innerHeight, | |
} | |
this.elements.forEach(block => { | |
const isInView = this.isInViewport(viewport, block) | |
this.options.toggleClass(block.link, isInView, this.options.classToToggle()) | |
}) | |
} | |
private isInViewport({ top: vTop, bottom: vBot }: Bounds, { top: eTop, bottom: eBot }: Bounds) { | |
const bottomShowing = eTop <= vTop && eBot > vTop | |
const encased = eTop > vTop && eBot < vBot | |
const topShowing = eTop > vTop && eTop < vBot | |
const covering = eTop > vTop && eTop < vBot | |
return bottomShowing || encased || topShowing || covering | |
} | |
private select(selector: string): Element[] { | |
return Array.from(document.querySelectorAll(selector)) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment