twbs / bootstrap

The most popular HTML, CSS, and JavaScript framework for developing responsive, mobile first projects on the web.
https://getbootstrap.com
MIT License
170.47k stars 78.83k forks source link

ScrollSpy not behaving correctly #36431

Open omar-abdul opened 2 years ago

omar-abdul commented 2 years ago

Scroll spy on body element will only activate the first item on the nav menu and will not update accordingly, Boostrap 5.1.3 works fine but for some reason 5.2 beta doesn't

crftwrk commented 2 years ago

Scrollspy changed completely in 5.2. I had the same problem and figured out that Scrollspy must be added to the wrapping element of the sections. This is usually NOT the <body>.

<body>
  <main>
    <div class="wrapper"> <!-- Add Scrollspy here -->
      <section id="one">
        ...
      </section>
      <section id="two">
        ...
      </section>
      <section id="three">
        ...
      </section>
    </div>
  </main>
</body>
mdo commented 2 years ago

Bug reports must include a live demo of the issue. Per our contributing guidelines, please create a reduced test case via CodePen or JS Bin and report back with your link, Bootstrap version, and specific browser and operating system details.


This is a saved reply.

github-actions[bot] commented 2 years ago

Hello @omar-abdul. Bug reports must include a live demo of the issue. Per our contributing guidelines, please create a reduced test case on CodePen or JS Bin and report back with your link, Bootstrap version, and specific browser and Operating System details.

mastercoms commented 2 years ago

Scrollspy has clearly regressed, even on the docs. The prior behavior was to make a link active if its associated element reached the scroll top. Now, it seems to move the active state if one element is more visible than the other. This does not get the same results, and has very strange behavior in various different scenarios which expected the previous behavior. This is just the coded behavior, it's not browser dependent.

Here is an example of the difference on the docs page itself.

https://user-images.githubusercontent.com/2672245/170849218-7faab47f-67e6-4295-8265-8544725b4d35.mp4

mastercoms commented 2 years ago

Here's one example of how it broke. This is just me making the paragraph longer on the 5.2 docs.

https://user-images.githubusercontent.com/2672245/170849451-0552fe36-70dd-4311-96e9-8de918a9631f.mp4

There's just no active link, since the heading is not visible. There is absolutely no way to handle this correctly with the current implementation, since if you have a wrapper element around each heading and paragraph, then it does not activate properly at all (since the large wrapper element will not satisfy the thresholds properly). And you can fiddle around with the root margin and maybe thresholds to help fit the content within the intersection parameters, but it will be specific to each section, so there is no setup that works with varying bits of spied content sections.

tranqt1984 commented 2 years ago

I have the same issue with version 5.2 regarding the ScrollSpy skipping to the newly observed element when scrolling. This does not provide a good user experience and will confuse the user as they are scrolling.

In this video, I updated the height from 200px to 600px which shows the same issue I'm having when using the ScrollSpy.

https://user-images.githubusercontent.com/62732616/173135284-76a83643-1b23-4448-b2c4-1eadb4db0d20.mov

POC: IntersectionObserver observing the element that is being scrolled into the viewport which mimics the ScrollSpy buggy behavior. https://codepen.io/byondsick12/pen/oNEdqzW

POC: This is my current solution... https://codepen.io/byondsick12/pen/wvyNqqd?editors=0111

github-actions[bot] commented 2 years ago

As the issue was labeled with awaiting-reply, but there has been no response in 14 days, this issue will be closed. If you have any questions, you can comment/reply.

julien-deramond commented 2 years ago

Reopened it since a CodePen has been provided by @tranqt1984 in the meantime (haven't checked its content yet)

kenrobbins commented 2 years ago

I noticed this issue as well. I have little experience with JS, but from what I read, it seems like with the smallest threshold at 0.1, that means that 10% of an element has to be inside the observer rectangle before the callback is called. That means if an element is more than 10x the height of the observer rectangle, this will never occur, which is why making the paragraph longer broke it.

Regarding the other issue with increasing the height of the spied element causing the spy to jump from 1 to 4 and then from 5 to 2, the observed rectangle is big enough that multiple elements are already in the observer rectangle, so the next one to enter it is not always the one immediately after the active element.

To solve this, I made the threshold 0 so that when any pixel enters the observer rectangle, the callback is called. To avoid multiple elements being inside the observer rectangle, I made the observer rectangle quite small and made the content elements have a minimum height. I placed the observer rectangle just below the top of the spied element. Whatever element is in this area should be highlighted. To do that, I set the root margin to -10% 0px -70%. I also set the minimum height of the content elements to half of the spied element. Depending on use case, I'm sure there are other techniques to accomplish the same.

Anyway, my hacky solution (again, I'm not a frontend programmer) is:

spy-container {
  min-height: calc(100vh / 2.0);
}
<main ... data-bs-root-margin="-10% 0px -70%">
  <div class="spy-container"></div>
  <div class="spy-container"></div>
  ...
    ss = bootstrap.ScrollSpy.getInstance("main");
    ss._getNewObserver = () => { 
      const options = {
        root: ss._rootElement,
        threshold: [0],
        rootMargin: ss._getRootMargin()
      };
      return new IntersectionObserver(entries => ss._observerCallback(entries), options);
    }
    ss._observer = null;
    ss.refresh();
GeoSot commented 2 years ago

@kenrobbins #36750 this may help you to use threshold properly

Khaldei commented 2 years ago

There are additional pieces that are not working here. They are visible directly on the demo site. For example, navigate to...

https://getbootstrap.com/docs/5.0/components/scrollspy/#item-1-2

Scroll to "Example with nested nav"

You will notice that the scrollspy navbar is now off by two elements.

"Item 1" in the navbar is now linked to "Item 1-2" image

If you scroll up to the "Item 1", nothing at all is highlighted. image

And if you scroll down, the offsets continue. image

qofe commented 2 years ago

Posting in case it helps anyone else -- As per the original post about Scrollspy not working on the 'body' element in 5.2. I was going through the steps to make a code-pen and found through that exercise it worked just fine. My issue was a conflict I had in the 'overflow' attribute on the 'body'. Once I cleared that up, it worked as expected.

shikkaba commented 2 years ago

My issue was a conflict I had in the 'overflow' attribute on the 'body'. Once I cleared that up, it worked as expected.

@qofe Can you elaborate on this?

iMattPro commented 1 year ago

Is there any update on this? Scrollspy and the Docs are awful now. Before Scrollspy when spying on a list of links, would highlight when the target reached the top of the viewport, but now it highlights just when the target is visible in the window anywhere. There's absolutely no guidance on how to tweak these root margins and thresholds to at least recreate the original scroll spy behavior from before 5.2 where it highlights when the target is at the top of the viewport.

usa-usa-usa-usa commented 1 year ago

I am experiencing the same issue. If a section is too short to consume enough of the vieweport, the nav item is never actually navigating.

I'd prefer that whatever is at the top of the screen is what is deemed "active".

SekarMurugesan commented 1 year ago

same issue i have in react when moving downwards it working but moving upward it is not working

suhailkc commented 1 year ago

Same issue. ScrollSpy is not working on

marcus-at-localhost commented 1 year ago

@shikkaba I experienced the same as @qofe. The issue, described in the very first post, is having something like this:

body{
    overflow-x: hidden; /* or auto or scroll */
}

and it was resolved by doing something like this:

body{
    overflow-x: unset; /* or initial */
}

I don't say that's generally the case for this not working, but overflow is likely the issue.

leroynas commented 1 year ago

I am actually working on a custom scroll-spy for a company. I was trying to get inspiration by checking the bootstrap implementation, however I found out that the IntersectionObserver only returns entries that changed in visibility. However it can be that there are 10 entries visible at load. These entries are processed, and the first entry is visible (almost always during load). When you scroll down, only entries that change in visibility are available so 11, then 12, then 13 (with entry.isIntersecting true). And likewise elements 1, then 2, then 3 (with entry.isIntersecting false).

On page load

|--------------------------------------------------|
|  item 1                                          |
|  item 2                                          |
|  item 3                                          |
|  item 4                                          |
|--------------------------------------------------|
   item 5
   item 6

new IntersectionObserver((entries) => {
  console.log(entries);
});

// Result: [
//   { id: 1, isIntersecting: true, ... },
//   { id: 2, isIntersecting: true, ... },
//   { id: 3, isIntersecting: true, ... },
//   { id: 4, isIntersecting: true, ... },
//   { id: 5, isIntersecting: false, ... },
//   { id: 6, isIntersecting: false, ... },
// ]

When scrolling to where item 6 is visible and item 1 isn't will result in this

   item 1
|--------------------------------------------------|
|  item 2                                          |
|  item 3                                          |
|  item 4                                          |
|  item 5                                          |
|--------------------------------------------------|
   item 6

new IntersectionObserver((entries) => {
  console.log(entries);
});

// Result:

// Result: [
//   { id: 1, isIntersecting: false, ... },
//   { id: 6, isIntersecting: true, ... },
// ]

This will trigger item 6 to be active where actually item 2 is the first visible item.

This means that solely depending on the IntersectionObserver is not possible. Also when there is nog change in visible elements it can be possible that another scroll-spy target should be made active.

A possible correct implementation would be to combine the IntersectionObserver with a onscroll where the IntersectionObserver is used to save all visible elements in some array/object and the onscroll callback uses these visible elements to determine which one is the first visible (of course only intersecting elements can be active), I expect this first option will reduce the load of the onscroll callback. Or only using a onscroll (basically going back to version 5.1).

Hope this can be of any help. I could share the vue composable implementation when I'm done however this will have to be adjusted for the needs of bootstrap (just inspiration).

qofe commented 1 year ago

My issue was a conflict I had in the 'overflow' attribute on the 'body'. Once I cleared that up, it worked as expected.

@qofe Can you elaborate on this?

Yes, I had `body {overflow-x:hidden; } in top of my styles. Removed overflow setting, changed nothing else, and that eliminated the scrollspy issue described in the first post.

davidtmiller commented 1 year ago

I have been getting better results when wrapping the entire section that I want to spy on with the id target.

For example, instead of doing this (example given in docs):

<h4 id="scrollspyHeading1">First heading</h4>
<p>...</p>

I will do this:

<div id="scrollspyHeading1">
    <h4>First heading</h4>
    <p>...</p>
</div>

This way the id target is in the viewport for most of the time. This does seem to mess things up a bit when I have one section that is smaller than another that is also within the viewport.

My implementation is very similar to the old Bootstrap 3 docs with nested nav items that appear when the parent has the active state (like the sidebar here https://getbootstrap.com/docs/3.4/components/) making it very important to have the parent being spied on at all time.

I played around with the rootMargin property as well, similar to what was done in the docs, bringing it to rootMargin: '0px 0px -35%' in my case which seemed to help.

Everything seems to be working when scrolling down, but when reaching the bottom of the page and scrolling back up, there are a lot of issues. The active item spied, when clicking on a menu link, after getting to the bottom of the page is inaccurate and sometimes no active item is picked up. Some items don't receive the active state at all, especially when scrolling back up the page versus clicking on a menu link. I am thinking that this has to do with the last scrollspy target section being very short in height, so it messes with the positioning when going back up.

Just some observations and suggestions in case they help progress this issue further!

Fatbat commented 1 year ago

I have been getting better results when wrapping the entire section that I want to spy on with the id target.

Yeah, I wrap the content with a section that references each scroll spy target and it doesn't help. The "Home" entry isn't working either. This has been broken for a while now.

iMattPro commented 1 year ago

So this issue just being tossed aside?

julien-deramond commented 1 year ago

So this issue just being tossed aside?

It's not being tossed aside. We're focusing right now on the v5.3.0 release. Then we'll try to define and prioritize what comes next. I'll keep in mind this one. Please understand that there's a huge amount of work to do in Bootstrap, and everybody does it in their spare time.

itchyny commented 1 year ago

In my use case, ScrollSpy does not work with sections with large height by default, and l noticed that the default threshold [0.1, 0.5, 1], typically 0.1, is not good for large height section . For me, data-bs-threshold="0,1" data-bs-root-margin="-30% 0% -70%" fixed the issue. (EDIT: Adding display: flow-root to the sections is also important to disable gaps between sections caused by child margins.)

gettonet commented 1 year ago

Is there any update on this? Scrollspy and the Docs are awful now. Before Scrollspy when spying on a list of links, would highlight when the target reached the top of the viewport, but now it highlights just when the target is visible in the window anywhere. There's absolutely no guidance on how to tweak these root margins and thresholds to at least recreate the original scroll spy behavior from before 5.2 where it highlights when the target is at the top of the viewport.

I completely agree and have the same issue. Following the topic...

iMattPro commented 1 year ago

So this issue just being tossed aside?

It's not being tossed aside. We're focusing right now on the v5.3.0 release. Then we'll try to define and prioritize what comes next. I'll keep in mind this one. Please understand that there's a huge amount of work to do in Bootstrap, and everybody does it in their spare time.

All I meant was, this Issue is no longer assigned to any project, so I see this falling through the cracks. It's already been an open issue and a problem for 15 months.

M-Yankov commented 9 months ago

Another example that scrollspy is not working in Bootstrap 5.3.2 Windows 10 64-bit Firefox 112.0, (MS Edge 120.0.2210.144) https://jsfiddle.net/2oxrkzyb/

The "Two" options doesn't get activated.

If you swap resources to 5.1.3 it's working fine.

bytes-commerce commented 8 months ago

What I figured is that the ScrollSpy seems to work when wrapping the seciont and making clear sections with that.

However, especially in responsive view (iPhone - roughly 400px) - it seems that very large sections are again not properly tracked by ScrollSpy. I am just checking out if it is an issue with the zIndex but that'd surprise me as of now.

For example my whole page has a total height of 16630 px, and the largest section clocks in with 7350 pixels. I'd add screenshots but its not giving much information in responsive view - one time the active class is added to a nav, one time it isn't.

peterstavrou commented 8 months ago

Has anyone managed to fix this? Even on the documentation page for v5.3 it's not working as it should.

cwildfoerster commented 6 months ago

any updates on this?

joshvillarreal commented 6 months ago

I am having similar issues!

sohammondal commented 5 months ago

Hello everyone, I am using this feature in a Nextjs SSR app and it was working sporadically for me. I had initially implemented it using the #via-data-attributes approach.

However when I switched initialisation to the #via-javascript approach it worked fine consistently.

On the client side, if you do something like this, it should work for you as well -


  useEffect(() => {

    import("bootstrap").then((bootstrap) => {
      new bootstrap.ScrollSpy("#sections-wrapper", {
        target: ".sidebar-nav",
      });
    });

  }, []);

If you still prefer the #via-data-attributes approach, then you would need to run getOrCreateInstance on the client side to get it working.

In general, its not a hack but an alternate approach as mentioned in the official documentation.

frontenddevguy commented 5 months ago

I also had to wrap the section in a div with the target id, rather than rely on headings being correctly detected. That improves the behaviour somewhat, and avoids there being 'dead space' between highlighted items when there are long chunks of text.

However, the position of scroll where the id is detected seems erratic, and even depends on speed of scroll.

frontenddevguy commented 5 months ago

So this issue just being tossed aside?

It's not being tossed aside. We're focusing right now on the v5.3.0 release. Then we'll try to define and prioritize what comes next. I'll keep in mind this one. Please understand that there's a huge amount of work to do in Bootstrap, and everybody does it in their spare time.

Please just revert it back to the previous working version

Fatbat commented 5 months ago

So this issue just being tossed aside?

It's not being tossed aside. We're focusing right now on the v5.3.0 release. Then we'll try to define and prioritize what comes next. I'll keep in mind this one. Please understand that there's a huge amount of work to do in Bootstrap, and everybody does it in their spare time.

It has now been more than two years.