Bootstrap: ScrollSpy skips short sections

Created on 1 Oct 2019  路  10Comments  路  Source: twbs/bootstrap

15729

This is still an issue that needs "solved" in Bootstrap 4

if the last section(s) are short, when reaching the bottom of the page, bootstrap will skip over anchors that didn't get scrolled to the top of the page and consider the last anchor to be "active"

confirmed help wanted js v4 v5

Most helpful comment

I stole the bootstrap 3 example and converted it to bootstrap 4:

https://codepen.io/bkdotcom/full/vYEyeVr

All 10 comments

Hi @bkdotcom can you provide a test case in v4 ?

Closing due to lack of response. You can comment here and we can re-open the issue.

This is an automated reply

I stole the bootstrap 3 example and converted it to bootstrap 4:

https://codepen.io/bkdotcom/full/vYEyeVr

Hello everyone! This problem seems to be heavily dependent on _where_ the short content is on the page. The example @bkdotcom provided accurately represents the case that led me to this issue in the first place. However, if you take that same example, slice the two short <sections> and re-insert them in the second and third slots within the container div, the scrollspy works correctly.

As best as I can tell, the current logic for determining the active <div> within the scrollspy is as follows:

  1. If the top of the scrollElement is at a location greater than or equal to the top of the scrollElement when you can't scroll anymore, make the last target active and return.
  2. Consider each possible target, working from the bottom of the page
  3. If the top of the scrollElement is beneath the top of the considered target AND the top of the scrollElement is beneath the top of the previously considered element, mark the current index as active.
  4. Else, change your target to the next element further up the page and repeat step 3.

(The logic is in the _process method, with the check on line 234 being the interesting case.

When stepping through the debugger on the provided example, it's clear that the bottom of the scrollElement is reached, triggering the short-circuit logic in case 1, before the smaller divs have a chance to have the top of their bounding box reach the top of the scrollElement which would make them active.

The key insight here is that you can't actually scroll through the total height of the scrollElement amount of space, but rather total_height - amount_viewable_at_any_given_time.

This suggests the trivial fix, which is just add an empty element with a height of _getOffsetHeight() as this would allow you to scroll _every_ target up to the top of the scrollElement. Obviously, this would leave a lot of white space and wouldn't provide a great UX.

One alternative would be calculate the percentage height that each target makes up of scrollElement height. Then, as the user scrolls, we can calculate the relative amount they've scrolled compared to the maximum possible scroll distance. Then, we can use the same comparison logic that's already in place to figure out what bucket we're in.

What do y'all think? I'm happy to put up a PR if we can agree on an approach.

@stevenbuccini would your proposed fix handle the case where you click on an anchor link, rather than scrolling to it manually? In the test case, for example, clicking on the penultimate "Cat ipsum" activates "Batman ipsum", even though it's not the one you clicked on. That behavior is less intuitive than when scrolling manually, in my opinion.

I can't say with certainty as I haven't looked at this in several months. However, it seems like clicking an anchor link directly should bypass the height offset calculations entirely and simply call _activate() on the appropriate target.

Unfortunately, that doesn't seem to be the case. From what I can tell, Scrollspy only responds to scroll events, so it doesn't know that the scrolling was caused by clicking an anchor link. Clicking on "Cat ipsum" in the example from @bkdotcom selects the link below it, "Batman ipsum".

It looks like clicking an anchor link triggers the scroll event which is why the ScrollSpy gets called and the wrong link is highlighted. However, the above link seems to suggest that perhaps the Scrollspy could check to see if there is valid value in window.location.hash to short-circuit the scroll height computation.

It looks like clicking an anchor link triggers the scroll event which is why the ScrollSpy gets called and the wrong link is highlighted. However, the above link seems to suggest that perhaps the Scrollspy could check to see if there is valid value in window.location.hash to short-circuit the scroll height computation.

That approach has some challenges, too. In @bkdotcom's example, clicking on a link doesn't actually add the hash to the URL, so I'm not sure if we can rely on that. When I run bootstrap/js/tests/visual/scrollspy.html from the current main (a 5.0.0 alpha), clicking a link does update the URL hash, but it also uses smooth scrolling, so the scroll event gets triggered multiple times. If you then scroll to another section manually, it keeps the old URL hash, so the short-circuiting would need to be in effect during that initial smooth scroll, but return to normal when it reaches the target or you scroll elsewhere.

Was this page helpful?
0 / 5 - 0 ratings