I have quite simple react application:
App.js:
render() {
return (
<BaseLayout />
);
}
BaseLayout:
import React, { useEffect, useState } from 'react';
import Navbar from "./Navbar";
import Section1 from "./section1/Section1";
import Section2 from "./section2/Section2";
import Section3 from "./section3/Section3";
import { Box, Grid } from "@mui/material";
import Fade from "react-awesome-reveal";
import { Waypoint } from 'react-waypoint';
where I simply display all sections at once and then show them with Fade
animation. All is good. But I also want to highlight the current displayed section on the Navbar
and change it accordingly during scrolling (currenlty highlighting happens only during clicking)
return (
<Box className={darkMode ? Style.dark : Style.light}>
<Grid
container
display={"flex"}
flexDirection={"column"}
minHeight={"100vh"}
justifyContent={"space-between"}
>
<Grid item>
<Navbar darkMode={darkMode} handleClick={handleToggleDarkMode} />
</Grid>
<Grid item flexGrow={1} className={Style.firstContentRow}>
<Section1 />
</Grid>
<Grid item flexGrow={1}>
<Fade bottom triggerOnce={true} className={Style.secondContentRow}>
<Section2 />
</Fade>
</Grid>
<Grid item flexGrow={1} className={Style.firstContentRow}>
<Fade bottom triggerOnce={true}>
<Section3 />
</Fade>
</Grid>
<Waypoint
onEnter={({ previousPosition, currentPosition, event }) => {
// PreviousPosition:below
// CurrentPosition:inside
// The above fields just return either `below` or `inside` and *NOT* for each section.
console.log("PreviousPosition:" + previousPosition);
console.log("CurrentPosition:" + currentPosition);
// I see no usefull data here as well, did I miss something?
console.log("Event:" + event);
}}
/>
</Grid>
</Box>
);
for this I think I need to use Waypoint
and some his event which should allow me to know which particular section is displayed now and then change a class name on a particular NavBar button accordingly. However, the information provided by waypoint event doesn't look enough. I can't find how can I understand which particular section is displayed at this point. Did I miss something? Also, the event is triggered not for each section, as I can see and only for the last one
<Waypoint/>
won't help to achieve what you want, it is not used for that, it is in fact a component that you can put inside your jsx.
Works in all containers that can scroll, including the window.
when you scroll, you can think of it as a point that once reached, left, or changes position, the corresponding event is triggered.
the previous position when you enter this point is always "below" here, because you have put <Waypoint/>
at the end of the grid.
the event
object is not useful as well for what you want, if you check event.doccument
you will find the whole scrollable element and not the last displayed element on the screen.
event - the native scroll event that triggered the callback. May be missing if the callback wasn't triggered as the result of a scroll.
so it is usually used
to build features like lazy loading content, infinite scroll, scrollspies, or docking elements to the viewport on scroll.
you can use three <Waypoint/>
each one before each section and a state to indicate which is the last waypoint entred (the order of the section displayed):
const [currentSection, setCurrentSection] = useState(1);
then create a useEffect
hook to run each time currentSection
is updated:
useEffect(() => {
console.log("current section is updated and this is its order ", currentSection);
// now you have the number indicates of the current section you can manage how to make your nav bar react to that
}, [currentSection]);
JSX:
<Box>
<Waypoint
onEnter={() => {
setCurrentSection(1);
}}
/>
<Grid
container
display={"flex"}
flexDirection={"column"}
minHeight={"100vh"}
justifyContent={"space-between"}
>
<Grid
item
flexGrow={1}
style={{ height: "800px", background: "red" }}
>
<div>section 1</div>
</Grid>
<Waypoint
onEnter={() => {
setCurrentSection(2);
}}
/>
<Grid
item
flexGrow={1}
style={{ height: "800px", background: "white" }}
>
<div>section 2</div>
</Grid>
<Waypoint
onEnter={() => {
setCurrentSection(3);
}}
/>
<Grid
item
flexGrow={1}
style={{ height: "800px", background: "green" }}
>
<div>section 3</div>
</Grid>
</Grid>
</Box>
I used to manage this with an event listener on scrolling and refs.
you want to give a ref
and an id
to each grid
section container, also a state to indicate which section is currently on the screen:
const firstRef = useRef();
const secondRef = useRef();
const ThirdRef = useRef();
const [currentSection, setCurrentSection] = useState();
when the component mounts we setCurrentSection
to the first section and set our event listener:
useEffect(() => {
setCurrentSection(firstRef.current);
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
the handleScroll
function will run each time you scroll and update the currentSection
state, but likely this will rerender the component only when currentSection
gets a new value :
const handleScroll = () => {
const scrollPosition = window.scrollY || document.documentElement.scrollTop;
const windowHeight = window.innerHeight;
const sectionPositions = [
firstRef.current,
secondRef.current,
ThirdRef.current
];
const currentSection = sectionPositions.find(
(section) =>
scrollPosition >= section.offsetTop &&
scrollPosition < section.offsetTop + windowHeight
);
if (currentSection) {
setCurrentSection(currentSection);
}
};
finally, we create a useEffect
hook that will be triggered each time the value of currentSection
is updated:
useEffect(() => {
if (currentSection) {
console.log("current section is updated and this is the id of the current one: ", currentSection.id);
// now you have the id of the current section you can manage how to make your nav bar react to that
}
}, [currentSection]);
const Test = () => {
const firstRef = useRef();
const secondRef = useRef();
const ThirdRef = useRef();
const [currentSection, setCurrentSection] = useState();
const handleScroll = () => {
const scrollPosition = window.scrollY || document.documentElement.scrollTop;
const windowHeight = window.innerHeight;
const sectionPositions = [ firstRef.current, secondRef.current, ThirdRef.current ];
const currentSection = sectionPositions.find(
(section) =>
scrollPosition >= section.offsetTop && scrollPosition < section.offsetTop + windowHeight
);
if (currentSection) {
setCurrentSection(currentSection);
}
};
useEffect(() => {
setCurrentSection(firstRef.current);
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
useEffect(() => {
if (currentSection) {
console.log("current section is updated and this is the id of the current one: ", currentSection.id );
// now you have the id of the current section you can manage how to make your nav bar react to that
}
}, [currentSection]);
return (
<div className="App">
<Box>
<Grid
container
display={"flex"}
flexDirection={"column"}
minHeight={"100vh"}
justifyContent={"space-between"}
>
<Grid
id={1}
item
flexGrow={1}
style={{ height: "800px", background: "red" }}
ref={firstRef}
>
<div>section 1</div>
</Grid>
<Grid
id={2}
item
flexGrow={1}
style={{ height: "800px", background: "white" }}
ref={secondRef}
>
<div>section 2</div>
</Grid>
<Grid
id={3}
item
flexGrow={1}
style={{ height: "800px", background: "green" }}
ref={ThirdRef}
>
<div>section 3</div>
</Grid>
</Grid>
</Box>
</div>
);
};