I want to implement a scrollspy without the bootstrap.
I have checked a lot of code online, all of them are implemented by jQuery.
How to implement the scrollspy only with the power of React?
I want to implement a scrollspy without the bootstrap.
I have checked a lot of code online, all of them are implemented by jQuery.
How to implement the scrollspy only with the power of React?
Share Improve this question edited Nov 12, 2019 at 13:51 vsync 130k59 gold badges340 silver badges421 bronze badges asked Feb 24, 2018 at 19:20 leuctionleuction 5752 gold badges8 silver badges19 bronze badges3 Answers
Reset to default 8I've made a React Wrapper (used with Render-Props):
⚡ Live Codepen Example
const {useState, useEffect, useCallback, useRef} = React;
/**
*
* @param {Object} scrollParent [DOM node of scrollable element]
* @param {Array} _targetElements [Array of nodes to spy on]
*/
const spyScroll = (scrollParent, _targetElements) => {
if (!scrollParent) return false;
// create an Object with all children that has data-name attribute
const targetElements =
_targetElements ||
[...scrollParent.children].reduce(
(map, item) =>
item.dataset.name ? { [item.dataset.name]: item, ...map } : map,
{}
);
let bestMatch = {};
for (const sectionName in targetElements) {
if (Object.prototype.hasOwnProperty.call(targetElements, sectionName)) {
const domElm = targetElements[sectionName];
const delta = Math.abs(scrollParent.scrollTop - domElm.offsetTop); // check distance from top, takig scroll into account
if (!bestMatch.sectionName)
bestMatch = { sectionName, delta };
// check which delet is closest to "0"
if (delta < bestMatch.delta) {
bestMatch = { sectionName, delta };
}
}
}
// update state with best-fit section
return bestMatch.sectionName;
};
/**
* Given a parent element ref, this render-props function returns
* which of the parent's sections is currently scrolled into view
* @param {Object} sectionsWrapperRef [Scrollable parent node React ref Object]
*/
const CurrentScrolledSection = ({ sectionsWrapperRef, children }) => {
const [currentSection, setCurrentSection] = useState();
// adding the scroll event listener inside this component, and NOT the parent component, to prever re-rendering of the parent component when
// the scroll listener is fired and the state is updated, which causes noticable lag.
useEffect(() => {
const wrapperElm = sectionsWrapperRef.current;
if (wrapperElm) {
wrapperElm.addEventListener('scroll', e => setCurrentSection(spyScroll(e.target)));
setCurrentSection(spyScroll(wrapperElm));
}
// unbind
return () => wrapperElm.removeEventListener('scroll', throttledOnScroll)
}, []);
return children({ currentSection });
};
function App(){
const sectionsWrapperRef = useRef()
return <CurrentScrolledSection sectionsWrapperRef={sectionsWrapperRef}>
{({ currentSection }) => <div ref={sectionsWrapperRef}>
<section
data-name="section-a"
className={currentSection === "section-a" ? 'active' : ''}
>Section A</section>
<section
data-name="section-b"
className={currentSection === "section-b" ? 'active' : ''}
>Section B</section>
<section
data-name="section-c"
className={currentSection === "section-c" ? 'active' : ''}
>Section C</section>
</div>
}
</CurrentScrolledSection>
}
ReactDOM.render(
<App />,
document.getElementById('root')
)
html, body, #root{ height: 95%; overflow:hidden; }
#root > div{
padding-bottom:20em;
height: 100%;
overflow:auto;
box-sizing: border-box;
}
section{
height: 50vh;
border-bottom: 1px solid red;
}
section.active{ background:lightyellow; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.0/umd/react-dom.production.min.js"></script>
<div id='root'></div>
It's not perfect because scroll direction does matter, since it implies on the intentions of the user.
Live running Demo
https://codesandbox.io/s/gallant-wing-ue2ks
Steps
1. Add the dependency
using npm
npm i react-scrollspy
or using yarn
yarn add react-scrollspy
2. Create your HTML sections
<section id="section-1">
<!-- Content goes here -->
</section>
<section id="section-2">
<!-- Content goes here -->
</section>
<!-- .... -->
<section id="section-n">
<!-- Content goes here -->
</section>
3. Add those section's ID to Scrollspy items
<Scrollspy items={ ['section-1', 'section-2', ..., 'section-n'] } />
4. Add some styles
Check this documentation https://makotot.github.io/react-scrollspy/#section-3
Check out react-ui-scrollspy
it is very easy to use.
Full disclosure: I maintain this package.
- In your navigation component
<div>
<p data-to-scrollspy-id="first">Section 1</p>
<p data-to-scrollspy-id="second">Section 2</p>
</div>
- Wrap the elements you want to spy on in the
<ScrollSpy>
component.
import ScrollSpy from "react-ui-scrollspy";
<ScrollSpy>
<div id="first">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Aut dolores
veritatis doloremque fugit. Soluta aperiam atque inventore deleniti,
voluptatibus non fuga eos magni natus vel, rerum excepturi expedita.
Tempore, vero!
</div>
<div id="second">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Aut dolores
veritatis doloremque fugit. Soluta aperiam atque inventore deleniti,
voluptatibus non fuga eos magni natus vel, rerum excepturi expedita.
Tempore, vero!
</div>
</ScrollSpy>
- Write styles for when the navigation element which is active in your
index.css
.active-scroll-spy {
background-color: yellowgreen;
border-radius: 15px;
}