Animated sticky header on scroll in React
Today we’ll show you how to create an animated sticky header on scroll in React JS without plugin. While you are working with react applications, it’s good practice to do not manipulate the real dom by injecting the JavaScript but we can manage it in a different way by using Refs. So the same concept we will use in this demo.
Building a sticky header component in React, Build a sticky navigation bar with React, Simple sticky/fixed header that animates on scroll, React Sticky Header Elements Component on Scroll in React, Building an animated sticky header with custom offset, How I linked animated headers to scroll position in React, react-sticky navbar on scroll, bootstrap sticky header on scroll.
Checkout more articles on ReactJS
In a previous article we have explained how to create sticky header on page scroll in JavaScript where we wrote a custom script to stick a header. Now in this article we will use useRef
& useEffect
to manage the sticky header using React Hooks. Let’s take an example.
Steps to create an animated sticky header on scroll
1. Create a sample website in React
Let’s set up a simple react application using create-react-app and create a sample website by adding the below HTML and CSS in app.
app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | import React from 'react'; function App() { return ( <div className="App"> <div className="header"> <h1>Sticky Header Website</h1> <p>A website created in HTML</p> </div> <div id="sticky-header" className="navbar"> <a href="#">Home</a> <a href="#">About</a> <a href="#">Contact</a> <a href="#" className="right">Login</a> </div> <div className="row"> <div className="main"> <h2>Lorem ipsum dolor sit amet</h2> <h5>Arcu dui vivamus arcu felis bibendum</h5> <img className="fakeimg" src="/images/img-1.jpg" /> <p>Excepteur sint occaecat</p> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ipsum suspendisse ultrices gravida dictum fusce. Mauris pellentesque pulvinar pellentesque habitant morbi. Libero id faucibus nisl tincidunt eget nullam non. Rhoncus aenean vel elit scelerisque mauris pellentesque pulvinar pellentesque habitant.</p> <br /> <h2>Vivamus arcu felis</h2> <h5>Tellus mauris a diam maecenas sed enim</h5> <img className="fakeimg" src="/images/img-2.jpg" /> <p>Ultrices tincidunt</p> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Augue interdum velit euismod in pellentesque. Vivamus arcu felis bibendum ut tristique et. Tincidunt tortor aliquam nulla facilisi cras.</p> <br /> <h2>Purus semper eget duis at tellus at urna</h2> <h5>Fermentum leo vel orci porta</h5> <img className="fakeimg" src="/images/img-3.jpg" /> <p>Tristique nulla aliquet</p> <p>Volutpat ac tincidunt vitae semper quis lectus nulla at volutpat. Dapibus ultrices in iaculis nunc sed augue. Senectus et netus et malesuada fames ac turpis.</p> <br /> <h2>Fermentum dui faucibus in ornare quam</h2> <h5>Viverra nibh cras pulvinar</h5> <img className="fakeimg" src="/images/img-4.jpg" /> <p>Scelerisque mauris pellentesque</p> <p>Scelerisque pellentesque pulvinar pellentesque habitant morbi tristique senectus et. Tempor orci eu lobortis elementum nibh tellus molestie nunc.</p> </div> <div className="side"> <h2>Eget mi proin</h2> <h5>Sed libero enim:</h5> <img className="fakeimg" src="/images/s-img-1.jpg" /> <p>Massa tincidunt dui ut ornare lectus sit amet est.</p> <h3>Mi ipsum faucibus</h3> <p>Lorem ipsum dolor sit ame.</p> <img className="fakeimg" src="/images/s-img-2.jpg" /> <h3>Malesuada fames</h3> <p>In nisl nisi scelerisque eu ultrices.</p> <img className="fakeimg" src="/images/s-img-3.jpg" /> </div> </div> <div className="footer"> <h2>Footer</h2> </div> </div> ); } export default App; |
index.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | body { font-family: Arial, Helvetica, sans-serif; margin: 0; } .header { padding: 80px; text-align: center; color: white; background-image: url("/images/head-img.jpg"); background-size: cover; } .header h1 { font-size: 40px; } .navbar { overflow: hidden; background: #269faf; } .navbar a { float: left; display: block; color: white; text-align: center; padding: 14px 20px; text-decoration: none; } .navbar a.right { float: right; } .navbar a:hover { background-color: #ddd; color: black; } .row { display: -ms-flexbox; display: flex; } .side { -ms-flex: 30%; flex: 30%; background-color: #f1f1f1; padding: 20px; } .main { -ms-flex: 70%; flex: 70%; background-color: white; padding: 20px; } .fakeimg { width: 100%; max-height: 300px; object-fit: cover; border-radius: 4px; } .footer { padding: 20px; text-align: center; background: #ddd; } .sticky { position: fixed; top: 0; width: 100%; transition: all 0.5s ease; animation: smoothScroll 1s forwards; } @keyframes smoothScroll { 0% { transform: translateY(-142px); } 100% { transform: translateY(0px); } } |
2. Implement react code for sticky header
Now we will initialize the state object and header reference. State objects will have two variables, the one used to enable the sticky header and the other used to set the margin of the component when we fix the header. Header reference will be used to access the real dom node. Check the following code.
1 2 | const [sticky, setSticky] = useState({ isSticky: false, offset: 0 }); const headerRef = useRef(null); |
Let’s create a scroll event handler that will be used to observe the window scroll position and compare it with header top offset and height.
1 2 3 4 5 6 7 8 | // handle scroll event const handleScroll = (elTopOffset, elHeight) => { if (window.pageYOffset > (elTopOffset + elHeight)) { setSticky({ isSticky: true, offset: elHeight }); } else { setSticky({ isSticky: false, offset: 0 }); } }; |
Use the above function to add on scroll event listeners. Also we have to remove it when the component will unmount.
1 2 3 4 5 6 7 8 9 10 11 12 13 | // add/remove scroll event listener useEffect(() => { var header = headerRef.current.getBoundingClientRect(); const handleScrollEvent = () => { handleScroll(header.top, header.height) } window.addEventListener('scroll', handleScrollEvent); return () => { window.removeEventListener('scroll', handleScrollEvent); }; }, []); |
In the above function, we have created one more function to pass in the event listener because otherwise it will not be removed on unmount.
Finally let’s use headerRef
and offset
to complete the example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | return ( <div className="App" style={{ marginTop: sticky.offset }}> . . . . . . <div id="sticky-header" className={`navbar${sticky.isSticky ? ' sticky' : ''}`} ref={headerRef}> <a href="#">Home</a> <a href="#">About</a> <a href="#">Contact</a> <a href="#" className="right">Login</a> </div> . . . . . . </div> ); |
3. Output
Let’s combine all code together and see the output.
app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | import React, { useState, useRef, useEffect } from 'react'; function App() { const [sticky, setSticky] = useState({ isSticky: false, offset: 0 }); const headerRef = useRef(null); // handle scroll event const handleScroll = (elTopOffset, elHeight) => { if (window.pageYOffset > (elTopOffset + elHeight)) { setSticky({ isSticky: true, offset: elHeight }); } else { setSticky({ isSticky: false, offset: 0 }); } }; // add/remove scroll event listener useEffect(() => { var header = headerRef.current.getBoundingClientRect(); const handleScrollEvent = () => { handleScroll(header.top, header.height) } window.addEventListener('scroll', handleScrollEvent); return () => { window.removeEventListener('scroll', handleScrollEvent); }; }, []); return ( <div className="App" style={{ marginTop: sticky.offset }}> <div className="header"> <h1>Sticky Header Website</h1> <p>A website created in HTML</p> </div> <div id="sticky-header" className={`navbar${sticky.isSticky ? ' sticky' : ''}`} ref={headerRef}> <a href="#">Home</a> <a href="#">About</a> <a href="#">Contact</a> <a href="#" className="right">Login</a> </div> <div className="row"> <div className="main"> <h2>Lorem ipsum dolor sit amet</h2> <h5>Arcu dui vivamus arcu felis bibendum</h5> <img className="fakeimg" src="/images/img-1.jpg" /> <p>Excepteur sint occaecat</p> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ipsum suspendisse ultrices gravida dictum fusce. Mauris pellentesque pulvinar pellentesque habitant morbi. Libero id faucibus nisl tincidunt eget nullam non. Rhoncus aenean vel elit scelerisque mauris pellentesque pulvinar pellentesque habitant.</p> <br /> <h2>Vivamus arcu felis</h2> <h5>Tellus mauris a diam maecenas sed enim</h5> <img className="fakeimg" src="/images/img-2.jpg" /> <p>Ultrices tincidunt</p> <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Augue interdum velit euismod in pellentesque. Vivamus arcu felis bibendum ut tristique et. Tincidunt tortor aliquam nulla facilisi cras.</p> <br /> <h2>Purus semper eget duis at tellus at urna</h2> <h5>Fermentum leo vel orci porta</h5> <img className="fakeimg" src="/images/img-3.jpg" /> <p>Tristique nulla aliquet</p> <p>Volutpat ac tincidunt vitae semper quis lectus nulla at volutpat. Dapibus ultrices in iaculis nunc sed augue. Senectus et netus et malesuada fames ac turpis.</p> <br /> <h2>Fermentum dui faucibus in ornare quam</h2> <h5>Viverra nibh cras pulvinar</h5> <img className="fakeimg" src="/images/img-4.jpg" /> <p>Scelerisque mauris pellentesque</p> <p>Scelerisque pellentesque pulvinar pellentesque habitant morbi tristique senectus et. Tempor orci eu lobortis elementum nibh tellus molestie nunc.</p> </div> <div className="side"> <h2>Eget mi proin</h2> <h5>Sed libero enim:</h5> <img className="fakeimg" src="/images/s-img-1.jpg" /> <p>Massa tincidunt dui ut ornare lectus sit amet est.</p> <h3>Mi ipsum faucibus</h3> <p>Lorem ipsum dolor sit ame.</p> <img className="fakeimg" src="/images/s-img-2.jpg" /> <h3>Malesuada fames</h3> <p>In nisl nisi scelerisque eu ultrices.</p> <img className="fakeimg" src="/images/s-img-3.jpg" /> </div> </div> <div className="footer"> <h2>Footer</h2> </div> </div> ); } export default App; |
That’s it for today.
Thank you for reading. Happy Coding..!!
Hi, thanks for your sharing! I have a question for you, hope you can help me.
I’d like to use style={{ marginTop: sticky.offset }} on another file.
I would like to use it on layout.js file inside the [main [here]] tags:
====================================
HEADER.JS
import Nav from ‘./nav’
import React, { useState, useRef, useEffect } from ‘react’
export default function Header() {
// [ Here your code ]
return (
[header className={“py-5 px-4″ + (sticky.isSticky ? ” sticky” : “”)} ref={headerRef}]
[div className=”max-w-screen-xl mx-auto”]
[Nav /]
[/div]
[/header]
);
}
====================================
LAYOUT.JS
import Head from ‘next/head’
import Footer from ‘./footer’
import Header from ‘./header’
export default function Layout({ children }) {
return (
[]
[Head]
[link rel=”icon” href=”/favicon.svg” /]
[/Head]
[Header /]
[main style={{ marginTop: sticky.offset }}]{children}[/main]
[Footer /]
[/]
);
}
====================================
How could I do? Thanks in advance.
Regards,
Cesar
You can pass the sticky object using redux or parent-child or child-parent communication.
Reference:
How to implement redux in React.js
How to pass data from Child component to Parent component in React
How to pass data from Parent component to Child component in React
Thank you so much!
I use Next.js and I solved changing “position: fixed;” to “position: sticky;” and removing “style={{ marginTop: sticky.offset }}”. I know that sticky isn’t supported by all browsers but for now is the simplest solution. I did well? Or should I remove more?
Thanks,
Regards
Good
Hello, does it possible make the navbar with responsive hamburger menu?
Do you want like this: https://www.cluemediator.com/how-to-toggle-the-sidebar-with-an-icon-in-react