Customize pinned element with IntersectionObserver
Learn how to detect when "sticky" element gets pinned

Search for a command to run...
Learn how to detect when "sticky" element gets pinned

No comments yet. Be the first to comment.
I already covered implementing Linear Regression model in Elixir from scratch. This time we'll move to implementing something fancy - Deep Learning model using a neural network under the hood! I'm going to use the same dataset so you'll have a nice 1...

In the previous post, I described how to create a Linear Regression Machine Learning model in Elixir from scratch. This time we'll discuss so similar yet different beast - Logistic Regression. Logistic Regression First things first - what is this fun...

In this article, I'll focus on getting as much value as possible from training data, particularly features. We'll continue our journey started in Linear Regression model using Elixir and Nx. Let's jump into the practice and learn how to squeeze lemon...

Learn how you can predict data using Linear Regression with Elixir

Find out what Machine Learning is about and what it looks in the Elixir's world

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.
<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.
.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!
Probably the simplest JS for this looks like this:
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.
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.
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.
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 🙂