Last active
November 2, 2022 19:23
-
-
Save mike-at-redspace/bf75716feea84f4d0d7b2c9c15e2c666 to your computer and use it in GitHub Desktop.
useScrollSpy
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
import React, { useRef } from 'react' | |
const App = () => { | |
const sectionRefs = [useRef(null), useRef(null), useRef(null)] | |
const activeSection = useScrollSpy({ | |
sectionElementRefs: sectionRefs, | |
offsetPx: -80 | |
}) | |
return ( | |
<> | |
<nav className='App-navigation'> | |
<span className={`item ${activeSection === 0 ? ' active' : ''}`}> | |
Section 1 | |
</span> | |
<span className={`item ${activeSection === 1 ? ' active' : ''}`}> | |
Section 2 | |
</span> | |
<span className={`item ${activeSection === 2 ? ' active' : ''}`}> | |
Section 3 | |
</span> | |
</nav> | |
<section className='App-section' ref={sectionRefs[0]}> | |
<h1>Section 1</h1> | |
</section> | |
<section className='App-section' ref={sectionRefs[1]}> | |
<h1>Section 2</h1> | |
</section> | |
<section className='App-section' ref={sectionRefs[2]}> | |
<h1>Section 3</h1> | |
</section> | |
</> | |
) | |
} | |
export default App |
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
import { useState, useEffect } from "react"; | |
const throttle = (callback, wait, immediate = false) => { | |
let timeout = null; | |
let initialCall = true; | |
return function () { | |
const callNow = immediate && initialCall; | |
const next = () => { | |
callback.apply(this, arguments); | |
timeout = null; | |
}; | |
if (callNow) { | |
initialCall = false; | |
next(); | |
} | |
if (!timeout) { | |
timeout = setTimeout(next, wait); | |
} | |
}; | |
}; | |
export default ({ | |
activeSectionDefault = 0, | |
offsetPx = 0, | |
scrollingElement, | |
sectionElementRefs = [], | |
throttleMs = 100 | |
}) => { | |
const [activeSection, setActiveSection] = useState(activeSectionDefault); | |
const handle = throttle(throttleMs, () => { | |
let currentSectionId = activeSection; | |
for (let i = 0; i < sectionElementRefs.length; i += 1) { | |
const section = sectionElementRefs[i].current; | |
// Needs to be a valid DOM Element | |
if (!section || !(section instanceof Element)) continue; | |
// GetBoundingClientRect returns values relative to viewport | |
if (section.getBoundingClientRect().top + offsetPx < 0) { | |
currentSectionId = i; | |
continue; | |
} | |
// No need to continue loop, if last element has been detected | |
break; | |
} | |
setActiveSection(currentSectionId); | |
}); | |
useEffect(() => { | |
const scrollable = scrollingElement?.current ?? window; | |
scrollable.addEventListener("scroll", handle); | |
// Run initially | |
handle(); | |
return () => { | |
scrollable.removeEventListener("scroll", handle); | |
}; | |
}, [sectionElementRefs, offsetPx, scrollingElement, handle]); | |
return activeSection; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment