"Troubleshooting a jittery React sticky header during scrolling: Dealing with IntersectionObserver and CSS

My current project involves implementing a sticky header in a React application using the Carbon framework. The goal is to have a sticky header with a bottom border when users scroll up, providing context for their viewing experience. However, when they scroll back down to the original page display, the header should no longer be sticky and shouldn't have a bottom border. I've utilized the Intersection Observer API for this functionality but have been encountering a flickering issue during scrolling. To manage this, I apply CSS classes based on the sticky state using debounced state updates.

If you have any recommendations or advice on how I can achieve the desired behavior, I would greatly appreciate it. You can view a video demonstration of the issue here.

You can also access the reproduction example on CodeSandbox (codesnadbox.io)

const [showStickyHeader, setShowStickyHeader] = useState(false);
const observer = useRef(null);
const debouncedSetShowStickyHeader = useRef(
  debounce((value) => setShowStickyHeader(value), 50)
).current;

const stickyMenuHeaderRef = useCallback(
  (node) => {
    if (observer.current) {
      observer.current.disconnect();
    }

    if (node) {
      observer.current = new IntersectionObserver(
        ([entry]) => {
          const shouldStick = entry.intersectionRatio < 0.95;
          debouncedSetShowStickyHeader(shouldStick);
        },
        {
          threshold: [0, 0.95, 1],
          rootMargin: '0px 0px -5px 0px',
        }
      );
      observer.current.observe(node);
    }

    return () => {
      if (observer.current) {
        observer.current.disconnect();
      }
      debouncedSetShowStickyHeader.cancel();
    };
  },
  [debouncedSetShowStickyHeader]
);
.resultsHeader {
  padding: var(--spacing-03) 0;
  z-index: 10;
  background-color: var(--openmrs-background-grey);
  top: var(--spacing-09);
  display: flex;
}

.stickyResultsHeader {
  padding: var(--spacing-03) 0;
  z-index: 10;
  background-color: var(--openmrs-background-grey);
  top: var(--spacing-09);
  display: flex;
  position: sticky;
  border-bottom: 1px solid #e1e1e1e1;
}

Answer №1

Solution Using Your Code

// Implementing a loop to set the sticky state based on intersection ratio
const shouldStick = entry.intersectionRatio < 0.95; 

Within your code, the condition for setting the sticky state depends on the value of entry.intersectionRatio. It is expected that this condition should not be less than 0.95 but should instead be exactly equal to 1.

const observer = new IntersectionObserver(
  ([entry]) => {
    if (entry.intersectionRatio < 0.95) {
      console.log('Element is less than 95% visible');
    } else {
      console.log('Element is at least 95% visible');
    }
  },
);

// When the sticky state is being set, the value will reach 1.0 which is greater than 0.95, causing an infinite loop in your implementation.

This behavior occurs because when you set the sticky state, entry.intersectionRatio equals 1, resulting in the loop.

// Update to only set true once
const shouldStick = entry.intersectionRatio === 1;

// Alternative approach, although somewhat unnecessary
const shouldStick = entry.intersectionRatio > 0.95;

Consider Using CSS for Sticky Positioning

The sticky positioning can be achieved purely through CSS without relying heavily on JS. By setting the position property to "sticky" and top to "0", the element will stick appropriately as intended while scrolling within its parent container.

const stickyResultsHeader = {
  // Styles for design
  padding: "1.5rem 0",
  backgroundColor: "gray",
  display: "flex",
  borderBottom: "1px solid #e1e1e1e1",
  // Applying sticky state
  position: shouldSticky ? "sticky" : "block", // Only downside is manual toggling back to sticky mode
  top: "0",
  zIndex: 10,
};
<div style={stickyResultsHeader}>
  <div style={leftSectionStyle}>
    <h4>Tests</h4>
    <Button kind="ghost" size="md">
      <span>Reset</span> <!-- Click event for setting shouldSticky to false -->
    </Button>
  </div>
</div>

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

Using sl-vue-tree with vue-cli3.1 on internet explorer 11

Hello, I am a Japanese individual and my proficiency in English is lacking, so please bear with me. Currently, I am using vue-cli3.1 and I am looking to incorporate the sl-vue-tree module into my project for compatibility with ie11. The documentation menti ...

Encountering a problem while trying to install npm packages in a Next.js application

Currently incorporating Clerk and NextJS into my project, with the latest versions of both installed. Encounter an error consistently when trying to add a new package. npm error code ERESOLVE npm error ERESOLVE could not resolve npm error npm error While r ...

What is the method for centering an input field with inline CSS?

Is there a way to center align an input text box on a webpage using only inline CSS? I have tried various techniques, including the following code snippet, but nothing seems to work: <input type="text" id="myInput" style= "margi ...

The state of a React Component does not reflect the changes when using setState on a property of a jsonObject

In my current task, I am focusing on three properties within a JSON object that return boolean values. updateChange=(value) =>{ //Creating a duplicate of the existing JSON Object let newState = JSON.parse(JSON.stringify(this.state.mainValue)); ...

Instructions for showcasing a 404 error page in the event that a back-end GET request to an API fails due to the absence of a user. This guide will detail the process of separating the

I am currently working on an application that combines JavaScript with Vue.js on the front-end and PHP with Laravel on the back-end. When a GET request is made from the front-end to the back-end at URL /getSummoner/{summonerName}, another GET request is t ...

Dynamic element substitution

Can you provide guidance on how to create a smooth transition in the height of a container based on the child element's height? Currently, my code does not include any animation effects. setTimeout(() => { document.getElementById("page1").st ...

Utilizing conditional styling in React: the ability to add or attach CSS classes based on

Is there a more efficient way to implement conditional formatting for this scenario? const PaginationStorePageLink = ({ store, pageNum }) => (observer(({ PaginationStore }) => { const className = this.props.store.currentPage === this.props.pageNum ...

What is the best approach to changing the color of an item in a cart once it has been selected?

Recently, I've been facing an issue with changing the color of an item after it has been added to the cart. Below are my functions for adding and removing items from the cart: function addToCart(newItem) { cartItems.map(item => newItem.ty ...

Tips for creating a responsive tab indicator in Material UI?

I successfully integrated react router with material-ui and the routing system is working as expected. Clicking on a tab routes you to the corresponding component. However, I am facing an issue where the blue underline indicator that typically accompanies ...

Ways to conduct a comparison of elements within an array within the same document

Is there a way to compare elements, by their index, within the same array in a MongoDB document? For example, consider the following entry: { "_id" : ObjectId("1"), "arr" : [ { "int" : 100 }, { "int" : 10 } ] } I have a collection with numerous similar e ...

What is the functionality of __proto__ when an object is initialized using Object.create(null)?

Take a look at this javascript snippet: var x = Object.create(null); x.bar = 2; var y = Object.create(x); console.log(y.bar); //outputs 2 console.log(y.__proto__); //outputs undefined y.__proto__ = null; console.log(y.__proto__); //outputs null console ...

Executing PHP Functions with Script Tags

I am trying to use PHP to output the <script></script> tag. Here is the code I am using: <?php echo "test"; echo "<br>"; echo '<script src="http://mywwebiste./mycode.js" type="text/javascript& ...

Adding data from one object to another in Javascript results in duplicated entries

Despite my efforts to find a solution for my issue, I couldn't locate a relevant topic. Being new to Javascript, I suspect my lack of understanding is hindering me from resolving the problem. After days of trying, I'm still unable to grasp it. An ...

Issues surrounding the determination of CSS attribute value using .css() function within a variable

I have been working on a function to change the color of a span dynamically from black to a randomly selected color from a predefined list. However, I am encountering an issue with the .css("color", variableName) part of my code and suspect that my synta ...

Tips for choosing the parents sibling using CSS

How can I change the background color of the .arrow-tip class when hovering over the first <li>? Can you provide me with the correct CSS rule? Here is the HTML code: <nav> <ul> <li>Item 1</li> <li>I ...

Fill the input fields with information stored in the vuex state within a vue component

Looking at the code snippet from my app component below: <template> <div> <h3>Basic</h3> <div v-for="(field, index) in basics" :key="index"> <input v-model="basics.name" placeholder="Name" type="text"> ...

Reposition DIV elements using only CSS styling

I'm attempting to switch the positions of two divs for responsive design (the website appearance changes based on browser width, ideal for mobile). Currently, my setup looks like this: <div id="first_div"></div> <div id="second_div"&g ...

Display requested tab feature using jQuery upon page load

I am new to jquery and have been using this code for creating tabs: <script type="text/javascript" charset="utf-8> $(function () { var tabContainers = $('div.tabs > div'); tabContainers.hide().filter(':first&apo ...

Add a line break tag (<br>) specifically for devices with a width less than 768 pixels

I have a lengthy subtitle that includes the following: Self-discovery | Personal development | Health prevention This is how it appears in HTML: <h2>Self-discovery <span class="divider"> | </span> Personal development <span class="d ...

Display or conceal child links using JQuery based on their availability

I have a query regarding JQuery. If I click on Link1, which does not contain any ul.children, the class current_page_item will be added (not shown in this code as it is added automatically by Wordpress). In this scenario, the ul.children in Link2 should be ...