# Customize pinned element with IntersectionObserver

Recently I run into an interesting issue: I wanted to customize a pinned element (`position: sticky`), at the moment when it gets pinned. It was a filter section, which I wanted to wrap up into a small bar. Thanks to that, users on mobiles have easy access to filters, even if they scrolled down to the very end of the items list.

The question is: **how to detect when the "sticky" element gets pinned**? The answer is: `IntersectionObserver`.

Let's consider the super simple case: we have `.filters-panel` div, containing the filter form. When a user scrolls down, the filters go out of view, we'd like to add `pinned` CSS class to `.filters-pannel`. The `.observer-point` element below is not here by accident. We'll discuss its purpose later.

```xml
<div class="filters-panel"> <!-- When it goes out of the viewport, add `pinned` class -->
  <form class="filters-form">
    <!-- FILTERS -->
  </div>
  <button class="show-filters">Show filters</button> <!-- Hidden by default -->
</div>
<div class="observer-point"></div>
```

Thanks to the `pinned` class, we can set the filters panel to hide the filter form, show the button `Show filters` and do some other styling. Roughly, the CSS (actually, let's go with Sass) could look like the one below.

```scss
.filters-panel
  +mobile
    &.pinned
      position: sticky
      form.filters-form
        display: none
      button.show-filters
        display: block

.observer-point
  height: 0px
```

You got the idea, right? So now, let's get this working!

## Detect disappearing element with IntersectionObserver

Probably the simplest JS for this looks like this:

```javascript
const observer = new IntersectionObserver((entries) => {
  const sortingPanel = document.querySelector(".filters-panel")
    if (entries[0].isIntersecting) {
       sortingPanel.classList.remove("pinned")
    }
    else  {
      sortingPanel.classList.add("pinned")
    }
})

observer.observe(document.querySelector('.observer-point'))
```

It could be explained like this: \*when the `.observer-point` element disappears, add `pinned` class to `.filters-panel`. Otherwise, remove it. \*

Pretty simple, but why does it observe some `.observer-point` instead of `.filters-pannel` directly? Basically (depending on the implementation), without `.observer-point`, it may run into an infinity loop like: *"Filters element disappears? Pin it! Oh, it is shown now... So "unpin it". Disappeared? Pin it!"* etc.

This issue causes flickering - an ugly one! A simple trick of observing an additional element like `.observer-point` solves the issue. Remember to set its height to `0px`. Otherwise, it won't work.

## Fixed navbar above? No problem!

Last but not least. When you have another element fixed to the top (most likely a navbar), just initialize the `IntersectionObserver` with `margin-top` set to the negative navbar's height. You can do this by passing `rootMargin` property as a second argument.

```javascript
const observer = new IntersectionObserver((entries) => {
  ...
}, { rootMargin: '-60px 0px 0px 0px' }) // assuming the navbar's height is 60px
```

With this trick, `.observer-point` **will be detected before disappearing under the navbar**.

I believe that's the simplest, but practical example of the use of **IntersectionObserver**. It has much more functionalities, you can read about them in the [documentation](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API).

I think it's a bit less intuitive than using scroll event and doing calculations on the fly... But it's more elegant and efficient. `scroll` event is so spammy - it's better to avoid it when you can 🙂
