Clue Mediator

Animated sticky header on scroll in React

📅May 7, 2020

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
  2. Implement react code for sticky header
  3. Output

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

import React from 'react';

function App() {
  return (
    <div class="App">
      <div class="header">
        <h1>Sticky Header Website</h1>
        <p>A website created in HTML</p>
      </div>

      <div id="sticky-header" class="navbar">
        <a href="#">Home</a>
        <a href="#">About</a>
        <a href="#">Contact</a>
        <a href="#" class="right">Login</a>
      </div>

      <div class="row">
        <div class="main">
          <h2>Lorem ipsum dolor sit amet</h2>
          <h5>Arcu dui vivamus arcu felis bibendum</h5>
          <img class="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 class="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 class="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 class="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 class="side">
          <h2>Eget mi proin</h2>
          <h5>Sed libero enim:</h5>
          <img class="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 class="fakeimg" src="/images/s-img-2.jpg">
          <h3>Malesuada fames</h3>
          <p>In nisl nisi scelerisque eu ultrices.</p>
          <img class="fakeimg" src="/images/s-img-3.jpg">
        </div>
      </div>

      <div class="footer">
        <h2>Footer</h2>
      </div>
    </div>
  );
}

export default App;

index.css

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.

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.

// 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.

// 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.

return (
  <div class="App" style={{ margintop: sticky.offset }}>
    . . .
    . . .
    <div id="sticky-header" class="{`navbar${sticky.isSticky" ?="" '="" sticky'="" :="" ''}`}="" ref={headerRef}>
      <a href="#">Home</a>
      <a href="#">About</a>
      <a href="#">Contact</a>
      <a href="#" class="right">Login</a>
    </div>
    . . .
    . . .
  </div>
);

3. Output

Let’s combine all code together and see the output.

app.js

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 class="App" style={{ margintop: sticky.offset }}>
      <div class="header">
        <h1>Sticky Header Website</h1>
        <p>A website created in HTML</p>
      </div>

      <div id="sticky-header" class="{`navbar${sticky.isSticky" ?="" '="" sticky'="" :="" ''}`}="" ref={headerRef}>
        <a href="#">Home</a>
        <a href="#">About</a>
        <a href="#">Contact</a>
        <a href="#" class="right">Login</a>
      </div>

      <div class="row">
        <div class="main">
          <h2>Lorem ipsum dolor sit amet</h2>
          <h5>Arcu dui vivamus arcu felis bibendum</h5>
          <img class="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 class="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 class="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 class="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 class="side">
          <h2>Eget mi proin</h2>
          <h5>Sed libero enim:</h5>
          <img class="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 class="fakeimg" src="/images/s-img-2.jpg">
          <h3>Malesuada fames</h3>
          <p>In nisl nisi scelerisque eu ultrices.</p>
          <img class="fakeimg" src="/images/s-img-3.jpg">
        </div>
      </div>

      <div class="footer">
        <h2>Footer</h2>
      </div>
    </div>
  );
}

export default App;

Output - Animated sticky header on scroll in React - Clue Mediator

Output - Animated sticky header on scroll in React - Clue Mediator

That’s it for today.
Thank you for reading. Happy Coding..!!

Demo & Source Code

Github Repository StackBlitz Project