React / 8 MIN READ
Conditional Hamburger Nav
Show hamburger on mobile with additional features
From the original Fervor library. Examples may use older package versions.
A basic tutorial that involves creating a responsive navigation bar that hides on scroll down and reappears on scroll up. The navigation bar will include a hamburger menu that displays on mobile devices and closes when you click outside of it.
Let’s start:
Step 1: Setting Up Your Project
- Initialize a new React project by running
npx create-react-app navbar-example - Change into the project directory
cd navbar-example - Start the development server
npm start
Step 2: Install Additional Dependencies
Install react-router-dom for handling navigation:
npm install react-router-dom
Step 3: Creating the Navbar Component
In the src directory, create a new file named Navbar.js. This component will display the navbar and control its visibility.
Paste the following code into Navbar.js:
import React, { useState, useEffect, useRef } from 'react';
import { Link } from 'react-router-dom';
const Navbar = ({ delay = false }) => {
const [isNavbarVisible, setIsNavbarVisible] = useState(!delay);
const [isMobile, setIsMobile] = useState(window.innerWidth < 700);
const [isHamburgerOpen, setIsHamburgerOpen] = useState(false);
const heroRef = useRef(null); // This ref will be used to observe the hero section
const handleResize = () => {
setIsMobile(window.innerWidth < 700);
};
useEffect(() => {
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
useEffect(() => {
if (delay) {
const timer = setTimeout(() => {
setIsNavbarVisible(true);
}, 500);
return () => clearTimeout(timer);
}
}, [delay]);
useEffect(() => {
const currentHeroRef = heroRef.current;
const observer = new IntersectionObserver(
([entry]) => {
setIsNavbarVisible(!entry.isIntersecting);
if (entry.isIntersecting && isHamburgerOpen) {
setIsHamburgerOpen(false);
}
},
{ threshold: 1 }
);
if (currentHeroRef) {
observer.observe(currentHeroRef);
}
return () => {
if (currentHeroRef) {
observer.unobserve(currentHeroRef);
}
};
}, [heroRef, isHamburgerOpen]);
const handleHamburgerClick = (e) => {
e.stopPropagation();
setIsHamburgerOpen(!isHamburgerOpen);
};
const closeMenu = () => {
setIsHamburgerOpen(false);
};
useEffect(() => {
if (isHamburgerOpen) {
document.addEventListener('click', closeMenu);
}
return () => {
document.removeEventListener('click', closeMenu);
};
}, [isHamburgerOpen]);
return (
<nav className={isNavbarVisible ? 'open' : ''}>
<h1 className="nav-title">
<span>The</span> BACK DOOR <span>Grill</span>
</h1>
<img className="nav-logo" src="../images/home/logo.png" alt="logo" />
{isMobile ? (
<div className="hamburger" onClick={handleHamburgerClick}>
<div></div>
<div></div>
<div></div>
{isHamburgerOpen && (
<ul className="nav-links">
<li><Link to="/">Home</Link></li>
<li><Link to="/menu">Menu</Link></li>
<li><Link to="/events">Events</Link></li>
<li><Link to="/about">About</Link></li>
</ul>
)}
</div>
) : (
<ul className="nav-links">
<li><Link to="/">Home</Link></li>
<li><Link to="/menu">Menu</Link></li>
<li><Link to="/events">Events</Link></li>
<li><Link to="/about">About</Link></li>
</ul>
)}
</nav>
);
}
export default Navbar;
Here’s a breakdown:
The hamburger menu visibility is controlled by the isMobile state variable:
const [isMobile, setIsMobile] = useState(window.innerWidth < 700);
When the width of the viewport is less than 700 pixels (i.e., window.innerWidth < 700), isMobile is set to true, and the hamburger menu is displayed.
This state is updated whenever the window is resized, thanks to the resize event listener in the useEffect hook:
useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth < 700);
};
window.addEventListener("resize", handleResize);
// Cleanup
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
So, the hamburger menu appears when the viewport width is less than 700px, and it disappears when the viewport width is 700px or more. This is a typical pattern for implementing responsive design, where a hamburger menu is used for navigation on smaller (usually mobile) devices, while a full navbar is shown on larger (usually desktop) screens.
now for the return statement
<nav className={isNavbarVisible ? 'open' : ''}>
: This creates a nav HTML element. The className of this nav element is determined by the isNavbarVisible state. If isNavbarVisible is true, then the className is 'open'. If isNavbarVisible is false, then className is an empty string.
{isMobile ? ( ... ) : ( ... )}
: This is a ternary operator. If isMobile is true, it will return the first JSX expression (the one after the ?). If isMobile is false, it will return the second JSX expression (the one after the :). In this case, it’s used to display different navigation links depending on whether the device is a mobile device or not.
<div className="hamburger" onClick={handleHamburgerClick}>
: This creates a div with the className of 'hamburger'. When this div is clicked, it calls the handleHamburgerClick function.
`{isHamburgerOpen && ( ... )}
: This is called short-circuit evaluation. If isHamburgerOpen is true, it will render the JSX expression after the &&. If isHamburgerOpen is false, it will render nothing.
`<ul className="nav-links">
: This creates an ul element with a className of 'nav-links'.
<li><Link to="/">Home</Link></li>
: This creates a li element that contains a Link component. The Link component is part of the react-router-dom library, and it’s used to create navigational links that work with the library’s Router. The to prop determines the URL path that the link points to.
Replace the comment with your navbar code, including the hamburger menu and links.
Step 4: Adding CSS for Navbar
Create a CSS file named Navbar.css in the src directory and import it in your Navbar.js file. Here you can add all the styling needed for your Navbar to function as expected. Make sure to include the styling that takes care of showing and hiding the Navbar based on the open class.
here is the CSS :
.hamburger {
margin-top: 3vh;
display: flex;
flex-direction: column;
justify-content: space-around;
width: 2rem;
height: 2.5rem;
cursor: pointer;
position: relative;
}
.hamburger div {
width: 2rem;
height: 3px;
background: black;
}
.hamburger .nav-links {
position: absolute;
top: 40px;
left: -70px;
background: var(--card-color-dark);
border-radius:5px 5px 10px 10px;
padding: 1rem;
width: fit-content;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
}
.hamburger .nav-links li {
list-style-type: none;
}
.hamburger .nav-links li a {
color: white;
text-decoration: none;
display: block;
padding: 12px 16px;
font-size: 1.2rem;
margin-block: 5px;
}
.hamburger .nav-links li a:hover {
background-color: #ddd;
}
nav.open {
transform: translateY(0);
}
Remember to adjust var(--card-color-dark) to a color of your choosing, or define the CSS variable --card-color-dark in a suitable location in your CSS.
let’s go over the CSS:
.hamburger {
margin-top: 3vh; /* Add vertical space above the hamburger icon equal to 3% of the viewport height */
display: flex; /* Align the div children (bars in the hamburger icon) in a row or a column */
flex-direction: column; /* Make the flex direction vertical (bars in the hamburger icon are stacked vertically) */
justify-content: space-around; /* Distribute the space evenly between the div children (bars in the hamburger icon) */
width: 2rem; /* Set the width of the hamburger icon */
height: 2.5rem; /* Set the height of the hamburger icon */
cursor: pointer; /* Change the cursor to a hand when hovering over the hamburger icon */
position: relative; /* This allows absolute positioning within the hamburger element for the dropdown menu */
}
.hamburger div {
width: 2rem; /* Set the width of each bar in the hamburger icon */
height: 3px; /* Set the height of each bar in the hamburger icon */
background: black; /* Set the color of each bar in the hamburger icon */
}
.hamburger .nav-links {
position: absolute; /* Position the dropdown menu independently of other elements, relative to the nearest positioned ancestor */
top: 40px; /* Move the dropdown menu down from the top of the hamburger icon */
left: -70px; /* Move the dropdown menu left from the left side of the hamburger icon */
background: var(--card-color-dark); /* Set the background color of the dropdown menu to a CSS variable defined elsewhere */
border-radius:5px 5px 10px 10px ; /* Round the corners of the dropdown menu */
padding: 1rem; /* Add some space around the content inside the dropdown menu */
width: fit-content; /* Make the width of the dropdown menu as large as the content it contains */
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); /* Add a shadow effect to the dropdown menu */
}
.hamburger .nav-links li {
list-style-type: none; /* Remove bullet points from the list items in the dropdown menu */
}
.hamburger .nav-links li a {
color: white; /* Set the color of the links in the dropdown menu */
text-decoration: none; /* Remove the underline from the links in the dropdown menu */
display: block; /* Make the links in the dropdown menu block elements */
padding: 12px 16px; /* Add some space around the text inside the links */
font-size: 1.2rem; /* Set the font size of the links */
margin-block: 5px; /* Add vertical margin before and after each link */
}
.hamburger .nav-links li a:hover {
background-color: #ddd; /* Change the background color of the links in the dropdown menu when you hover over them */
}
The hamburger icon is made up of three divs (the bars of the hamburger), which are displayed vertically and evenly spaced due to the flex and justify-content properties. The dropdown navigation links are absolutely positioned within the relatively positioned hamburger container, which means they are positioned relative to the hamburger container, not the entire page.
Step 5: Using the Navbar Component
In src/App.js, import and use the Navbar component at the top of your component tree.
import React from 'react';
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Navbar from './Navbar';
import Home from './Home';
import About from './About';
import Menu from './Menu';
import Events from './Events';
function App() {
return (
<Router>
<Navbar delay={true} />
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/menu">
<Menu />
</Route>
<Route path="/events">
<Events />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</Router>
);
}
export default App;
In this example, I’ve assumed that you have Home, About, Menu, and Events components. Replace these with your actual components.
Now, when you start your app with npm start, you should see your responsive navbar in action! When you scroll down, the navbar should hide, and it should reappear when you scroll back up. The hamburger menu should display on mobile devices and close when you click outside of it.