I wanted a way to show a top navigation bar in the hero section and a hamburger drop-down navigation as a user scrolls down the rest of the page as part of my React single page application.
After a lot of Googling along with some trial and error, I put together a few ideas to achieve my desired end result which looks like this:
Getting Started
I used:
-
react-scroll
npm i react-scroll -
react-burger-menu
npm i react-burger-menu - Two separate components: one for top nav and one for the drop-down nav
Setting Up Burger Menu
Instructions for burger set up and customization are here. Here is what my Sidenav.js and Sidenav.css files looked like after tweaking the default:
import React, {Component} from 'react';
import './Sidenav.css';
import { slide as Menu } from 'react-burger-menu';
import {Link} from 'react-scroll';
class Sidenav extends Component {
render () {
return (
<div className='sidenav'>
<Menu width={ '15%' } >
<Link className='nav-li' style={{ cursor:
"pointer"}} to="home" spy={true} smooth=
{true}>Home</Link>
<Link className='nav-li' style={{ cursor:
"pointer"}} to="about" spy={true} smooth=
{true}>About</Link>
<Link className='nav-li' style={{ cursor:
"pointer"}} to="work" spy={true} smooth=
{true}>Work</Link>
<Link className='nav-li' style={{ cursor:
"pointer"}} to="contact" spy={true} smooth=
{true}>Contact</Link>
<a className='nav-li' target='_blank' rel="noopener
noreferrer" href='linktoyourblog'>Blog</a>
</Menu>
</div>
);
}
}
export default Sidenav;
/* Position and sizing of burger button */
.bm-burger-button {
position: fixed;
width: 30px;
height: 25px;
left: 10px;
top: 36px;
}
/* Color/shape of burger icon bars */
.bm-burger-bars {
background-color: #EFC6BA;
}
/* Position and sizing of clickable cross button */
.bm-cross-button {
height: 24px;
width: 24px;
}
/* Color/shape of close button cross */
.bm-cross {
background: #222222;
}
/*
Sidebar wrapper styles
Note: Beware of modifying this element as it can break the animations - you should not need to touch it in most cases
*/
.bm-menu-wrap {
position: fixed;
height: 100%;
}
/* General sidebar styles */
.bm-menu {
background: #FFFFFF;
padding: 2.5em 0.5em 0;
font-size: 1.15em;
}
/* Morph shape necessary with bubble or elastic */
.bm-morph-shape {
fill: #FFFFFF;
}
/* Wrapper for item list */
.bm-item-list {
color: #FFFFFF;
padding: 0.8em;
}
/* Individual item */
.bm-item {
display: inline-block;
}
/* Styling of overlay */
.bm-overlay {
background: #FFFFFF;
}
/* Styling of links */
.nav-li {
font-family: 'Playfair Display';
color: #000000;
outline: none;
padding: 10%;
text-decoration: underline;
text-transform: uppercase;
}
/* Removes purple outline when clicked */
.nav-li:focus {
outline: none;
}
.nav-li:hover {
text-decoration: none;
font-weight: bold;
}
Managing State
At this point, the burger menu is working. There are additional state and event handlers available which are useful. By adding the following above render(), the side menu will close when a link is clicked rather than having to click on the 'X':
state = {
menuOpen: false,
}
handleStateChange (state) {
this.setState({menuOpen: state.isOpen})
}
// closes menu on link click
closeMenu () {
this.setState({menuOpen: false})
}
Next, I added props to the Menu and Link components in return().
- Menu receives
isOpen={this.state.menuOpen} onStateChange={(state) => this.handleStateChange(state)} - Link (and
<a>) receivesonClick={() => this.closeMenu()}
<Menu width={ '15%' } isOpen={this.state.menuOpen}
onStateChange={(state) => this.handleStateChange(state)}>
<Link className='nav-li' style={{ cursor: "pointer"}}
to="home" spy={true} smooth={true} onClick={() =>
this.closeMenu()}>Home</Link>
<Link className='nav-li' style={{ cursor: "pointer"}}
to="about" spy={true} smooth={true} onClick={() =>
this.closeMenu()}>About</Link>
<Link className='nav-li' style={{ cursor: "pointer"}}
to="work" spy={true} smooth={true} onClick={() =>
this.closeMenu()}>Work</Link>
<Link className='nav-li' style={{ cursor: "pointer"}}
to="contact" spy={true} smooth={true} onClick={() =>
this.closeMenu()}>Contact</Link>
<a className='nav-li' target='_blank' rel="noopener
noreferrer" href='linktoyourblog' onClick={() =>
this.closeMenu()}>Blog</a>
</Menu>
Alright! The burger menu is fully functional now.
So... how do we only make it visible as a user scrolls away from the landing view?
componentDidMount() and onscroll events
componendDidMount() is invoked immediately after a component is mounted. As this happens we will watch for a scroll event to initiate a state change which changes the <div> opacity.
In Sidenav.js:
state = {
menuOpen: false,
opacity: '0'
}
// will show sidenav when scroll position is above 500
componentDidMount() {
if (typeof window !== 'undefined') {
window.onscroll = () => {
let currentScrollPos = window.pageYOffset;
let maxScroll = document.body.scrollHeight -
window.innerHeight;
if (currentScrollPos < 500 && currentScrollPos <
maxScroll) {
this.setState({ opacity: '0'})
// to get position: console.log(currentScrollPos)
} else {
this.setState({ opacity: '1' })
}
}
}
}
What's happening here? I'm using state and inline CSS styling to control the visibility of the drop-down menu. I added opacity to our state and give it a default value of 0 so that it does not display on page load. In the function, I use setState to change the opacity based on the current scroll position of the page.
Let's go back to return(). All of the content should be wrapped in a <div className='sidenav>, to which we add inline styling:
<div className='sidenav' style={{ opacity: `${this.state.opacity}`}}>
...
</div>
That's it! Now the drop-down is only visible after a certain point on the page view.
Conclusion & Addressing Performance
A somewhat hodgepodge interactive navigation design for SPA's.
After doing more research on componentDidMount() and onscroll events, I noticed some concerns about performance issues using this type of approach. In its current usage, this isn't completely killing my app's performance.
Upon running a Lighthouse audit I get:

Although, that is a drop from the previous 97 on Performance and 100 on Best Practices. 👀
That being said, I'd love to have a discussion on performance or perhaps a more efficient way to implement this interactive navigation that I'm not aware of-- so if you have something to teach me, drop a comment below! 💭

Top comments (1)
Don't you think people should resist google and their ranking of everything? We're basically bowing down to some machine, basically a deity. We should't care what it has to say. If you made a website you're happy with and it works that should be the end of it.