Implement a Dynamically Displayed Header With Window & Element Scroll Positions
Tags: webdev, javascript, tutorial
Published: 2023-02-27 12:00:00

One of the most frequently seen patterns in web design is to hide the header on page load until the user scrolls and when the page hits a specific point.

You may choose to use jQuery or vanilla Javascript to implement this but whichever tool you use, you need to be able to obtain the top/bottom positon of the target element relative to the scroll position of the page.

In this tutorial, we'll get our hands dirty and build a simple page with a header and some content. The header is not shown at page load but when the user scrolls the page to a certain point, it appears. When he/she scrolls past a certain point, the header becomes invisible again.

## Table of Contents

- [An Overview of Our Example](#an-overview-of-our-example)
- [HTML & CSS](#html---css)
- [Query and Store Elements](#query-and-store-elements)
- [Element.getBoundingClientRect()](#elementgetboundingclientrect--)
- [window.scrollY](#windowscrolly)
- [Event Listener](#event-listener)
- [Conclusion](#conclusion)

## An Overview of Our Example

This is what we'll build:

![The example we'll build](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/akckecmdael2cewvl5i2.gif)

<figcaption>The example we'll build</figcaption>

The header is only visible when the page is scrolled within a range. 


![The behavior of the header](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5omdw5omg1gngd6kacc0.png)


<figcaption>The behavior of the header</figcaption>


## HTML & CSS

Let's prepare our html code and give it some CSS first.

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    section {
      margin-bottom: 100px;
    }
    div {
      background-color: black;
      color: white;
      display: flex;
      justify-content: center;
      padding: 300px 0;
    }
    ul {
      list-style: none;
      display: flex;
      justify-content: flex-end;
      margin: 0;
      padding: 0;
    }
    li a {
      display: block;
      padding: 20px;
    }
    header {
      width: 100%;
      visibility: hidden;
      opacity: 0;
      position: fixed;
      top: 0;
      left: 0;
      background-color: white;
      transition: all 0.5s ease-in-out;
    }
    header.show {
      visibility: visible;
      opacity: 1;
    }
  </style>
</head>
<body>
  <header>
    <nav>
      <ul>
        <li>
        <a href="#section1">Section1</a>
      </li>
      <li>
        <a href="#section2">Section2</a>
      </li>
      <li>
        <a href="#section3">Section3</a>
      </li>
      </ul>
    </nav>
  </header>
  <main>
    <section id="section1">
      <div>Section 1</div>
    </section>
    <section id="section2">
      <div>Section 2</div>
    </section>
    <section id="section3">
      <div>Section 3</div>
    </section>
    <section id="section4">
      <div>Section 4</div>
    </section>
    <section id="section5">
      <div>Section 5</div>
    </section>
  </main>
  <script>
    // todo: implement header behavior.
  </script>
</body>
</html>
```

At this point, nothing will happen when you scroll the page because we haven't implemented our Javascript code yet. We'll do this in the next steps.

## Query and Store Elements

Let's start by querying and storing the relevant elements in variables as we'll need them later.

```html
...
<script>
  const section2 = document.querySelector('#section2')
  const section3 = document.querySelector('#section3')
  const header = document.querySelector('header')
</script>
```

Next comes one of the key steps in our implementation. We want to add the `show` class name to our header when the top of section2 hits the top of the window as we scroll and remove the class name when the bottom of section3 reaches the top of the window. To achieve this, we need 3 values:

1. **The y position of the top of section2** 
2. **The y position of the bottom of section3**
3. **How much the document is scrolled**

To obtain value 1 and 2, I'm going to call the `getBoundingClientRect()` method on section2 and section3 elements and to get value 3, I'll use `scrollY` of the `window` object.

## Element.getBoundingClientRect()

This method returns an object containing the following properties:

- **left**: pixels from the left of the viewport to the left of the element
- **top**: pixels from the top of the viewport to the top of the element
- **right**: pixels from the left of the viewport to the right of the element
- **bottom**: pixels from the top of the viewport to the bottom of the element
- **x**: equal to the value of _left_
- **y**: equal to the value of _top_
- **width**: width of the element including padding and border
- **height**: height of the element including padding and border

You can see that this method is very powerful because we can get the position and size of an element in one shot. Check out [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) to learn more about it.

We'll need the *top* value of section2 and *bottom* value of section3. Add the following code:

```html
...
<script>
  ...
  // add these
  const {top: top_} = section2.getBoundingClientRect()
  const {bottom: bottom_} = section3.getBoundingClientRect()
</script>
```
## window.scrollY

This property of the `window` object gives us the number of pixels the document is scrolled vertically.

To achieve the expected behavior of our header, we'll check `window.scrollY` against the `top_` and `bottom_` to decide whether to add or remove the `show` class name in the next step.

## Event Listener

Now it's time to wire up our scroll event listener.

```html
...
<script>
  ...
  // add these
  window.addEventListener('scroll', function() {
    if (window.scrollY >= top_ && window.scrollY <= bottom_) {
      if (!header.classList.contains('show')) {
        header.classList.add('show')
      }
    } else {
      if (header.classList.contains('show')) {
        header.classList.remove('show')
      }
    }
  })
</script>
```

And the full Javascript code looks like this.

```html
<script>
  const section2 = document.querySelector('#section2')
  const section3 = document.querySelector('#section3')
  const header = document.querySelector('header')

  const {top: top_} = section2.getBoundingClientRect()
  const {bottom: bottom_} = section3.getBoundingClientRect()

  window.addEventListener('scroll', function() {
    if (window.scrollY >= top_ && window.scrollY <= bottom_) {
      if (!header.classList.contains('show')) {
        header.classList.add('show')
      }
    } else {
      if (header.classList.contains('show')) {
        header.classList.remove('show')
      }
    }
  })
</script>
```

That's it. Open the page in your browser and make sure it works as expected. If anything goes wrong, double check your code.

## Conclusion

In this tutorial, we built a simple page with a dynamic header that appears and disappears based on the scroll position of the document.

At the core of the implementation are the `Element.getBoundingClientRect()` method and the `window.scrollY` property. You can also make use of them to implement things like _back-to-top_ button which only appears when user scrolls past a certain point.