Skip to content

Conversation

@DenisLopatin
Copy link

@DenisLopatin DenisLopatin commented Dec 9, 2025

Description

Closes: #41900

Closes: #36431
Closes: #41361
Closes: #40526
Closes: #36912

Since it covers (makes the same changes) that and #41726
Closes: #39198
Closes: #37858
Closes: #39248
Fix: #35566

The redesigned ScrollSpy component, which passed all the tests (which were also modified, supplemented, and checked manually and automatically).

Motivation & Context

The component was broken when switching to the observer api. The tests did not signal a violation of functionality, as there were no checks of boundary cases. I decided to delve into the mechanism of the component and find a solution to the problem.

Type of changes

Core Proposal: My proposal focuses on restoring the logic where an element is considered active as soon as it reaches the top edge of the viewport. The previous departure from this behavior led to user dissatisfaction.

Technical Approach: I propose implementing a "laser pointer" method—configuring a narrow detection zone along the top edge of the screen.

This ensures that the element currently positioned at the top of the viewport is always marked as active.

This approach maintains high component performance, as the IntersectionObserver callback is triggered infrequently and only when necessary (eliminating noise).

Structural Recommendation: Furthermore, wrapping anchor targets within container elements is the optimal strategy. This facilitates smoother state transitions, ensures accurate detection of the active element upon page reload, and delivers a significantly better overall user experience (UX).

I decided to leave users access to low-level observer api mechanisms such as rootMargin and threshold, but they should use it at their own risk, since everything works out of the box in the current implementation.

I also decided to leave the offset parameter, because the main way to use rootMargin would be to set the upper bound. This is what he does and is needed for settings that will help you easily adjust the component when the navigation element is floating.

I checked all the examples locally, and then added them to the documentation along with the description.

This version needs to be checked. It would be very frivolous of me to assume that everything would work perfectly, but I tried to do it that way.

  • Bug fix (breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Refactoring (breaking change)
  • Breaking change (fix or feature that would change existing functionality)

Checklist

  • I have read the contributing guidelines
  • My code follows the code style of the project (using npm run lint)
  • My change introduces changes to the documentation
  • I have updated the documentation accordingly
  • I have added tests to cover my changes
  • [] All new and existing tests passed (The HTML validation tests of the ScrollSpy documentation page failed, but since this is a suggestion, I haven't adjusted anything there yet [vnu-jar validation])

Live previews

Related issues

All related tasks are described above

- changes the rootMargin and threshold parameters as the basis for tracking the current item
- refactoring while maintaining the semantics
- all tests passed except "should be cleared if they are listed above the first section"
@DenisLopatin DenisLopatin requested a review from a team as a code owner December 9, 2025 19:00
@DenisLopatin
Copy link
Author

The main message is that you need to wrap anchors in containers and link to them already. Then everything will work as it should, quickly, productively, conveniently, smoothly, and so on. The documentation doesn't do this at the moment (for the main navigation menu), so it will mess up when scrolling fast.

Before:

<h4 id="list-item-9">Item 9 (Interruption)</h4>
<p>Brief interruption expanded. Donec sollicitudin molestie malesuada. Nulla quis lorem ut libero malesuada feugiat. Pellentesque in ipsum id orci porta dapibus. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a.</p>
<p>Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Proin eget tortor risus. Nulla quis lorem ut libero malesuada feugiat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla porttitor accumsan tincidunt. Curabitur aliquet quam id dui posuere blandit.</p>
<p>Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Praesent sapien massa, convallis a pellentesque nec, egestas non nisi. Donec sollicitudin molestie malesuada.</p>

After:

<div id="list-item-9">
  <h4>Item 9 (Interruption)</h4>
  <p>Brief interruption expanded. Donec sollicitudin molestie malesuada. Nulla quis lorem ut libero malesuada feugiat. Pellentesque in ipsum id orci porta dapibus. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a.</p>
  <p>Curabitur non nulla sit amet nisl tempus convallis quis ac lectus. Proin eget tortor risus. Nulla quis lorem ut libero malesuada feugiat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla porttitor accumsan tincidunt. Curabitur aliquet quam id dui posuere blandit.</p>
  <p>Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Vivamus magna justo, lacinia eget consectetur sed, convallis at tellus. Praesent sapien massa, convallis a pellentesque nec, egestas non nisi. Donec sollicitudin molestie malesuada.</p>
</div>

@DenisLopatin

This comment was marked as resolved.

@DenisLopatin

This comment was marked as resolved.

@XhmikosR
Copy link
Member

This is impossible to review.

@XhmikosR XhmikosR closed this Dec 10, 2025
@DenisLopatin
Copy link
Author

This is working, but your current component is not working. It's simple

@DenisLopatin
Copy link
Author

And it won't work if you don't use the tools I've described here. As well as the tests will continue to pass and will not catch erroneous behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment