How to Create a Fixed Header Which Animates on Page Scroll

In this tutorial, we’ll learn how to create a pattern seen on many websites these days: a fixed header which animates to a less obtrusive state as we scroll down the page. We’ll start with the basic structure, then get things working using CSS and pure JavaScript. Before closing, we’ll briefly cover how we can optimize our code as well as discussing challenges present when applying this kind of pattern to touch devices.

To get an idea of what we’re going to build, here’s the demo (you may prefer fullscreen view):

HTML Markup

We’ll start this exercise with the following markup–a header, containing a <nav> and a couple of other nested elements:

 1 
 2   22 
 23 24 
 25   26 


The nav element, which is part of the header, contains three elements; the logo, the main menu, and a placeholder button to trigger a responsive menu (below 1061px).

Note: If you click on this button, nothing much will happen. Creating the responsive menu is beyond the scope of this tutorial.

Initial CSS Styles

Now let’s have a look at some CSS styles to get things moving:

 1 header {  2  position: fixed;  3  top: 0;  4  width: 100%;  5  padding: 20px;  6  box-sizing: border-box;  7  background: #DD3543;  8 }  9 10 nav {  11  display: flex;  12  align-items: flex-end;  13  justify-content: space-between;  14  transition: align-items .2s;  15 }  16 17 .logo {  18  font-size: 2rem;  19  display: inline-block;  20  padding: 20px 30px;  21  background: #F35B66;  22  color: #fff;  23  margin: 50px 0 0 50px;  24  transition: all .2s;  25 }  26 27 ul {  28  display: flex;  29  margin: 50px 50px 0 0;  30  padding: 0;  31  transition: margin .2s;  32 }  33 34 li:not(:last-child) {  35  margin-right: 20px;  36 }  37 38 li a {  39  display: block;  40  padding: 10px 20px;  41 }  42 43 .toggle-menu {  44  display: none;  45  font-size: 2rem;  46  color: #fff;  47  margin: 10px 10px 0 0;  48  transition: margin .2s;  49 }  50 51 main {  52  display: block;  53  padding: 0 20px;  54 } 

Here’s a brief explanation of the most important rules here:

• The header element is a fixed positioned element.
• We use flexbox to layout the nav element.
• The logo has margin-top: 50px and margin-left: 50px. Additionally, we give it padding: 20px 30px.
• The main menu mirrors the logo, with margin-top: 50px and margin-right: 50px.
• The responsive link button is hidden. It becomes visible when the viewport width is less than 1061px. Moreover, we set its top and right margins to 10px.
• We add the transition property to the elements whose property values will change in the future. In this way, we achieve a smooth transition effect between the initial state and the final state.

With these rules in place, the header looks like this:

So far we’ve built the basic structure of our header. It’s now time to discuss the next steps:

• The main element should be positioned right underneath the header. Remember that the header has positioned: fixed, and it’s therefore positioned on top of the main element.
• The header should be animated as we scroll down the page.

To solve the first task, we add a padding-top property to the main element. The value of this property should be equal to the header’s height. In our case, we haven’t specified a fixed height for our header, so we’ll use some JavaScript to calculate it, and then add the corresponding padding to the main element.

To solve the second task, we’ll do the following:

1. Retrieve the number of pixels that the document has already been scrolled vertically.
2. If this number is greater than 150px, we assign the scroll class to the header.

JavaScript

Here’s the required JavaScript code–we begin by defining some variables, calculating the height of the header, then adding that value as padding-top to the main element:

 1 var m = document.querySelector("main"),  2  h = document.querySelector("header"),  3  hHeight;  4 5 function setTopPadding() {  6  hHeight = h.offsetHeight;  7  m.style.paddingTop = hHeight + "px";  8 } 

For this demonstration, we use the offsetHeight property to retrieve the header’s height. Keep in mind that we could equally have used the getBoundingClientRect() method. It’s worth mentioning that this method may return fractional values.

Now onto the scrolling event:

 1 function onScroll() {  2  window.addEventListener("scroll", callbackFunc);  3  function callbackFunc() {  4  var y = window.pageYOffset;  5  if (y > 150) {  6  h.classList.add("scroll");  7  } else {  8  h.classList.remove("scroll");  9  }  10  }  11 } 

Here we take advantage of the window’s pageYOffset property to calculate the number of pixels that our document has been scrolled vertically. Note that this property doesn’t work in older versions of IE (< 9). However, if you want to support any of these versions, a workaround is available here.

Then we use the classList property to add and remove the scroll class from our header. Not all browsers support this property however, so if you want to support any of these you may want to look at the classList.js and classie.js polyfills. For this example, we could have used the className property to manipulate our single class, but in a real-world scenario this might not the ideal solution (in case we have multiple classes).

To wrap things up, we call our functions in two different cases:

• and as we resize the browser window.
 1 window.onload = function() {  2  setTopPadding();  3  onScroll();  4 };  5 6 window.onresize = function() {  7  setTopPadding();  8 }; 

CSS

As long as our scrolling exceeds the limit of 150px, a few additional CSS rules take place:

 1 .scroll {  2  box-shadow: 0 7px 0 0 rgba(0, 0, 0, .1);  3 }  4 5 .scroll .logo {  6  padding: 10px 20px;  7  font-size: 1.5rem;  8 }  9 10 .scroll nav {  11  align-items: center;  12 }  13 14 .scroll .logo,  15 .scroll ul,  16 .scroll .toggle-menu {  17  margin: 0;  18 } 

Specifically, we make the following changes:

• Reduce the logo’s padding and font size.
• Change the alignment for the flex items across the cross-axis.
• Remove the margin from the logo, the menu, and the responsive link button.

The aforementioned rules result in this new header layout:

Going Responsive

As we’ve mentioned in a previous section, when the viewport width is less than 1061px, we hide the menu and show the responsive link button (which doesn’t actually do anything). Plus, we make a few other changes in the target elements.

Below you can see the initial appearance of the responsive header:

Here are the related CSS rules:

 1 @media screen and (max-width: 1060px) {  2  header {  3  padding: 10px;  4  }  5  nav {  6  align-items: center;  7  }  8  ul {  9  display: none;  10  }  11  .logo {  12  font-size: 1.8rem;  13  margin: 10px 0 0 10px;  14  }  15  .toggle-menu {  16  display: block;  17  }  18 } 

And here’s the header’s appearance once it’s been animated:

Performance Considerations

Now that we managed to give our header the desired behavior, let’s go one step further and discuss a few things about performance.

In our example, the code that manipulates the header’s animation (i.e. callbackFunc) is executed when the scroll event fires. This means it can be triggered hundreds of times or more. This may result in performance issues, especially when the callbackFunc function contains a lot of stuff that should be done as we scroll up and down the page. In our specific case we’re dealing with a simple animation, but imagine a real-world scenario where we want to do more complex things like re-positioning elements and so on.

So what can we do about this? Well, there are many possible solutions, but for now let’s briefly discuss one of them. Specifically, we want to allow our function to be executed at most once every 200 milliseconds (this is an arbitrary value). To implement this functionality, we’ll take advantage of Lodash, a modern JavaScript utility library. This library provides the throttle function we need.

First, we include the library (happily there’s also the option to incorporate only the desired function) in our project, then we replace this code:

window.addEventListener("scroll", callbackFunc);

with this:

window.addEventListener("scroll", _.throttle(callbackFunc, 200));

To understand the difference, let’s make a simple test. We’ll initialize a counter that is incremented each time the callbackFunc function is triggered.

Now try to scroll up and down the page. When you do so, you’ll notice that the counter’s value changes roughly every 200ms. Next, repeat this process without using Lodash. You’ll notice that the counter’s value changes much faster.

Again, although this optimization seems unnecessary for our simple example, you should take it into consideration when you’re dealing with expensive and repetitive tasks.

In the same way, we could have optimized the resize event listener.

Browser Support

This effect works in most recent browsers and devices. However, the experience isn’t ideal in all mobile devices (e.g. iOS devices). This happens because the scroll event behaves differently on desktop browsers compared to mobile devices. For example, on a desktop browser the scroll event fires continuously, whereas on an iPad it occurs when you swipe and release your finger

Knowing the issues above, you should decide whether or not it’s a sensible pattern to use on your web pages.

Conclusion

In this tutorial, we created a fixed header which is animated as we scroll down the page. I hope you liked the demo and you’ll use it as inspiration in your coming projects!