Tips for swapping out a sticky element as you scroll

Context

As I work on developing a blog website, I aim to integrate a sticky element that dynamically updates according to the current year and month as users scroll through the content. This would provide a visual indication of the timeline for the listed blog articles.

When it comes to coding, my approach involves experimenting with HTML first, followed by CSS if necessary, and then JS as a last resort. I find this method efficient as it leverages existing features and minimizes the utilization of computing resources. However, I'm open to other perspectives on this practice.

Problem

The challenge lies in altering the style of elements when they become 'stuck'. In attempting this, I reviewed David Walsh's solution which utilizes IntersectionObserver, but encountered glitches when applying it to multiple elements simultaneously.

The primary issue I am facing is that when dealing with multiple entries, the script erroneously identifies an element as 'pinned' when it reaches the bottom border of the window.

Code

Below is a snippet of the code, along with a corresponding jsfiddle demo.

//Implementing David Walsh's code using a loop

document.querySelectorAll(".myElement").forEach((i) => {
const observer = new IntersectionObserver(([i]) => i.target.classList.toggle("is-pinned", i.intersectionRatio < 1),
{threshold: [1]});
observer.observe(i);
})
#parent { 
  height: 2000px; 
}

.myElement {
  position: sticky;
  top: -1px;
}

/* styles applied when the header is in sticky mode. The transition times contribute to an undesired effect */
.myElement.is-pinned {
  color: red;
  transition: color 0.3s, background-color 0.3s;
  background-color: orange;
}
<div id="parent">
  <!-- Including more than one 'hello' element. Adding br tags to introduce vertical space exceeding viewport height -->
  <br><br><br><br>
  <div class="myElement">Hello!</div>
  
  <br><br><br><br>
  <div class="myElement">Hello 2!</div>
  
  <br><br><br><br>
  <div class="myElement">Hello 3!</div>
  
  <br><br><br><br>
  <div class="myElement">Hello 4!</div>
  
  <br><br><br><br>
  <div class="myElement">Hello 5!</div>
  
  <br><br><br><br>
  <div class="myElement">Hello 6!</div>
  
  <br><br><br><br>
  <div class="myElement">Hello 7!</div>
  
  <br><br><br><br>
  <div class="myElement">Hello 8!</div>
</div>

Answer №1

It is important to note that you only require a single IntersectionObserver. If you have the same callback and options for multiple elements (which is the case here), you can use the same observer to observe() all of them. The only action that needs to be inside the loop is observer.observe(i);.

With this setup, the single observer can handle multiple entries simultaneously when scrolling up or down the page. It is necessary to iterate through all the observed entries in such scenarios.

Additionally, it is crucial to understand that the intersectionRatio does not differentiate between the top and bottom visibility of an element on the screen. Elements are considered 100% visible when they cross the threshold at either end of the box.

In most cases, you are interested in the elements at the top of the box. To address this, utilize the IntersectionObserverEntry object's boundingClientRect property to determine the current position of the element. This way, you can selectively toggle elements based on their visibility at the top.

To summarize, your code should look like this:

const observer = new IntersectionObserver((entries) => {
    for (let i of entries) {
        i.target.classList.toggle(
            "is-pinned", i.boundingClientRect.y < 0);
    }
}, {threshold: [1]});

document.querySelectorAll(".myElement").forEach(i => observer.observe(i));

However, there is still a potential issue with your implementation. In cases where the scrolling container is long enough to skip from the very top to the very bottom, some elements may transition from "0% visibility below the box" to "99% visibility at the top of the box," without triggering the IntersectionObserver callback. As a result, these elements do not receive the is-pinned class.

You can overcome this problem by adding another threshold of 0% to the existing observer to capture these transitions as well:

const observer = new IntersectionObserver((entries) => {
    for (let i of entries) {
        i.target.classList.toggle(
            "is-pinned", i.boundingClientRect.y < 0);
    }
}, {threshold: [0, 1]});

document.querySelectorAll(".myElement").forEach(i => observer.observe(i));

By incorporating this dual threshold approach, elements transitioning from visible to sticky or invisible to sticky will now correctly have their classes toggled.

Answer №2

Your last line in the JS code has a mistake. Correct it by replacing it with:

document.querySelectorAll(".myElement").forEach((item) => {
  const observer = new IntersectionObserver(
  ([entry]) => entry.target.classList.toggle("is-pinned", entry.intersectionRatio < 1), {
    threshold: [1]
  });
  observer.observe(document.querySelector(".myElement")); // Ensure to use the element instead!
})

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Trouble with clearTimeout function

//Account Creation - Register Button Event $('#registerCreateAccount').on('click', function() { var username = $('#registerUsername').val(); var email = $('#registerEmail').val(); var age = $('#regis ...

What could be the reason behind ng-bind-html only displaying text and not the link?

Utilizing ng-repeat to exhibit a list on my webpage. One of the fields in my data contains a URL that I want to display as an actual link within my HTML page. Please refer to the screenshots below: My HTML: My rendered page: I have included the angular- ...

Issue with Bootstrap checkbox/switch

Trying to determine the status of a checkbox - whether it's checked or not. The default state is unchecked, but I need to perform actions when it is checked and also when it gets unchecked. Currently using bootstrap 5.3.0 alpha html <div clas ...

Two-column dropdowns without the use of tables

I am looking to create a drop-down menu with the following structure: | Heading ---------------- action | Item action | Item action | Item action | Item The action could represent "Change" and Item could be something like "Users". To maintain ...

The React-native application initialized using npx create-react-app encountered an error and could not launch

Hello there, A couple of months back, I developed an app using create-react-app. However, when I tried to run it with npm start, I encountered the following error message: react-scripts start It seems like there's an issue with the project depende ...

Margin not being applied to last element in parent - any ideas why?

Why does the margin of the last element, whether it is a <p> or <h1>, not have any impact? It should be pushing the background of the parent container downwards. .container { background: grey; } h1 { margin-bottom: 3em } p { margin- ...

Choose a different option when there is a change

Here is an example of JSON data: [{ "user_id": "113", "employe_first_name": "Asaladauangkitamakan", "employe_last_name": "Nasibb" }, { "user_id": "105", "employe_first_name": "Ryan", "employe_last_name": ...

Retrieving a single post from a JSON file using AngularJS

I'm currently delving into AngularJS, but I seem to be stuck on what might be a simple issue. At the moment, I have some hardcoded JSON files with a few persons in them and no actual backend set up yet. In my form, I aim to display a single person ea ...

Incorporate a new node module into your Ember CLI application

I am interested in integrating the Node.js module https://www.npmjs.com/package/remarkable-regexp into my Ember-CLI project. Can someone guide me on how to make it accessible within the Ember application? I attempted to do so by including the following c ...

Retrieving cookie from chrome.webRequest.onBeforeSendHeaders

I have been developing a Firefox add-on with the goal of intercepting HTTP requests and extracting the cookie information. While I was successful in extracting the 'User-agent' data from the header, I faced difficulties when attempting to extract ...

Exploring Vue's reactivity using the composition API and cloning props

In my current component setup, I am receiving props from a parent. However, I am facing an issue where I only want to clone these props without any changes affecting the parent component. <template> <input-text v-model="form.name" /&g ...

Using Node.js and TypeScript to define custom data types has become a common practice among developers

I offer a variety of services, all yielding the same outcome: type result = { success: boolean data?: any } const serviceA = async (): Promise<result> => { ... } const serviceB = async (): Promise<result> => { ... } However, th ...

What is the best method for passing parameters from HTML to AJAX within code?

My project involves working with Flask, HTML, and Ajax. Here is the HTML code snippet: <script type=text/javascript> $(function() { $('a#calculate').bind('click', function() { $.getJSON('/_add_numbers&ap ...

The ts-node encountered an issue with the file extension in this message: "TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension

When using ts-node, I encountered the following error: $ ts-node index.ts TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /home/projects/node-hddds8/index.ts I attempted to remove "type": "module& ...

The entry '0-0' already exists for the key 'local_part', please enter a unique value

Creating a simple API to handle GET, POST, DELETE, and UPDATE requests. The GET method is functioning correctly, but encountering an issue with the POST method. When attempting to post data, an error is being encountered: error: Error: ER_DUP_ENTRY: ...

Choosing the Right Project for Developing HTML/Javascript Applications in Eclipse

Whenever I attempt to build a webpage using eclipse, I am presented with two choices: -- A Javascript project -- A Static web project If I opt for the former, setting up run to open a web browser can be quite challenging. If I decide on the latter ...

Create a customized menu with jQuery that disappears when hovered over

Check out this menu I've created: http://jsbin.com/useqa4/3 The hover effect seems to be working fine, but I'm looking to hide the div #submenuSolutions when the user's cursor is not on the "Solution" item or the submenu. Is there a way to ...

Exploring jQuery Mobile - What Causes an Empty State?

Using $.mobile.navigate("#test-page", {id:123}) for navigation to a secondary page seems to be successful. The transition between pages is smooth.... but the state remains empty! According to the documentation, the state should contain all necessary info ...

Having trouble with Angular2's Maximum Call Stack Exceeded error when trying to display images?

I am facing an issue in my Angular2 application where I am trying to display an image in Uint8Array format. The problem arises when the image size exceeds ~300Kb, resulting in a 'Maximum Call Stack Exceeded' error. Currently, I have been able to ...

Check Image Dimensions prior to Image Loading

I've been working with JQuery Masonry and would like to incorporate Lazy Load using a WordPress plugin to load images only when they come into view. The issue I'm facing is that when Lazy Load is used, the masonry elements don't recognize t ...