I found an example in Codepen of what I am wanting to do in my React Application. When I scroll down the the page it should highlight that section in my navbar, which is sticky at the top of the page. My app is a single page that contains about 5 to 6 sections that will be index/inline pages, so as the user scrolls down, it will take them to that next section. What approach should I take to incorporate this? More specifically, should I just use React, should I implement React Router Dom? What is the best approach to acplish this functionality in the most general sense?
App
import React, {Component} from 'react';
import Footer from './ponents/footer/Footer';
import Hero from './ponents/hero/Hero';
import Resume from './ponents/resume/Resume';
import About from './ponents/about/About';
import Contact from './ponents/contact/Contact';
import Navbar from './ponents/navbar/Navbar';
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {
}
}
ponentWillMount() {
window.addEventListener('scroll', this.navHighlighter);
}
navHighlighter() {}
render() {
return (
<div className="App">
<Navbar />
<Hero/>
<About/>
<Resume/>
<Contact/>
<Footer/>
</div>
);
}
}
Navbar
mport React, {Component} from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import { faBars } from '@fortawesome/free-solid-svg-icons'
import './navbar.css';
class Navbar extends Component {
constructor() {
super();
this.state = {
addClass: '',
showMenu: ''
}
this.handleScroll = this.handleScroll.bind(this);
this.toggleMenu = this.toggleMenu.bind(this);
}
ponentDidMount() {
window.addEventListener("scroll", this.handleScroll)
}
handleScroll() {
if (window.scrollY > 0) {
this.setState({addClass: 'sticky'})
} else {
this.setState({addClass: ''})
}
}
toggleMenu() {
let { showMenu } = this.state;
this.setState({showMenu: showMenu === 'open' ? '' : 'open'})
}
render() {
return (
<div>
<header onScroll={this.handleScroll} className={this.state.addClass}>
<a href="#" className="logo">Jeremy</a>
<nav className={this.state.showMenu}>
<ul>
<li><a href="#" className="active">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#skills">Skills</a></li>
<li><a href="#projects">Projects</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
<div className="menu-toggle" onClick={this.toggleMenu}><FontAwesomeIcon className='icon' icon={faBars} /></div>
</header>
</div>
);
}
}
export default Navbar;
I found an example in Codepen https://codepen.io/dbilanoski/pen/LabpzG?editors=1010 of what I am wanting to do in my React Application. When I scroll down the the page it should highlight that section in my navbar, which is sticky at the top of the page. My app is a single page that contains about 5 to 6 sections that will be index/inline pages, so as the user scrolls down, it will take them to that next section. What approach should I take to incorporate this? More specifically, should I just use React, should I implement React Router Dom? What is the best approach to acplish this functionality in the most general sense?
App
import React, {Component} from 'react';
import Footer from './ponents/footer/Footer';
import Hero from './ponents/hero/Hero';
import Resume from './ponents/resume/Resume';
import About from './ponents/about/About';
import Contact from './ponents/contact/Contact';
import Navbar from './ponents/navbar/Navbar';
import './App.css';
class App extends Component {
constructor() {
super();
this.state = {
}
}
ponentWillMount() {
window.addEventListener('scroll', this.navHighlighter);
}
navHighlighter() {}
render() {
return (
<div className="App">
<Navbar />
<Hero/>
<About/>
<Resume/>
<Contact/>
<Footer/>
</div>
);
}
}
Navbar
mport React, {Component} from 'react';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import { faBars } from '@fortawesome/free-solid-svg-icons'
import './navbar.css';
class Navbar extends Component {
constructor() {
super();
this.state = {
addClass: '',
showMenu: ''
}
this.handleScroll = this.handleScroll.bind(this);
this.toggleMenu = this.toggleMenu.bind(this);
}
ponentDidMount() {
window.addEventListener("scroll", this.handleScroll)
}
handleScroll() {
if (window.scrollY > 0) {
this.setState({addClass: 'sticky'})
} else {
this.setState({addClass: ''})
}
}
toggleMenu() {
let { showMenu } = this.state;
this.setState({showMenu: showMenu === 'open' ? '' : 'open'})
}
render() {
return (
<div>
<header onScroll={this.handleScroll} className={this.state.addClass}>
<a href="#" className="logo">Jeremy</a>
<nav className={this.state.showMenu}>
<ul>
<li><a href="#" className="active">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#skills">Skills</a></li>
<li><a href="#projects">Projects</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
<div className="menu-toggle" onClick={this.toggleMenu}><FontAwesomeIcon className='icon' icon={faBars} /></div>
</header>
</div>
);
}
}
export default Navbar;
Share
Improve this question
asked Aug 23, 2020 at 7:06
Jeremy MarkJeremy Mark
831 gold badge1 silver badge5 bronze badges
4 Answers
Reset to default 3Create refs
for each of your sections say sampleRef
that you need to keep track of. With the sampleRef
you can get position of section by using sampleRef.current.offsetTop
. This gives absolute position of elements in the page.
To get position of page use window.scrollY
. Then you can pare offsetTop
of all the sections with scrollY
. The section which has smallest difference between offsetTop
and scrollY
is the active section.
To find that I use divide and conquer to optimize the pare method.
I have create a codesandbox check it here. There you can checkout the file ScrollHighlightNabbar.js
I just had the same exact problem too and this is how I implemented it.
- Create forward refs for each of your sections.
- In your onScroll event handler, create the logic for making a nav item active. Use the previously created refs to access the height properties of your section.
- Set the active/inactive states of your nav items. In my case I made refs of the nav items and edited the classes directly through them instead of relying on states because I wanted to prevent a rerender on each scroll.
const aboutRef = useRef();
useEffect(() => {
const handleScroll = (e) => {
if (window.scrollY >= 0 && window.scrollY <= window.innerHeight / 2) {
// Set states for nav items here if the user is on the first section
} else if (aboutRef.current.offsetTop - window.scrollY < window.innerHeight / 2 && stuffRef.current.offsetTop - window.scrollY >= window.innerHeight / 2) {
// For the about section
} else {
// Etc...
}
}
document.addEventListener('scroll', handleScroll);
return () => {
document.removeEventListener('scroll', handleScroll);
}
}, [])
return (
<nav>
...
<li className={stateActiveAbout ? 'active' : 'not-active'}>About</li>
...
</nav>
<About ref={aboutRef} .../>
)
Something to that effect. I'm sure the code can be improved upon but I hope you get the idea. The above is a functional ponent so you'll have to convert yours.
I'm not quite sure if this approach violates any React 'rules' but it works.
The best and easiest method I got was:
First: Add a CSS property
scroll-behaviour:smooth;
to Html and body tags, this will make the scrolling smooth when section HashLink is clicked.Second: The auto highlight of Nav-links can be done by a using the
react-scrollspy-highlight
package it will do the task perfectly. goto https://www.npmjs./package/react-scrollspy-highlight for docs.
You can use <NavLink />
with activeClassName
attribute.
<NavLink to="#about" activeClassName={"active"} className={"anything"}>About</NavLink>