[BUG] Race condition in upward scrolling compensation system could fire scrollBy when user is scrolling
See original GitHub issueDescribe the bug Upward scrolling compensation might kick in while the user is still scrolling. This is noticeable on lower-end devices and when the components might take time to mount.
Reproduction Reproduction is tricky. I made a demo with variable-height elements to ensure there is a decent amount of compensation and added some synchronous calculations to the render function to slow the components down. Originally noticed in Firefox on Windows while testing lower-end devices, confirmed in a VM. https://healthy-thunder-sunflower.glitch.me (using glitch.me because I needed to compare it to a github fork). Depending on how fast your computer is, you might want to increase the value in the component to slow things more though can’t guarantee it would help. Almost impossible to repro on macOS Firefox. Definitely not as obvious as in the video, no matter the slowdown.
Looking at the code, deviation pipe filters out new values that have isScrolling === true. The question is why does isScrolling become false during scrolling.
Briefly looking at stateFlagsSystem, scroll end is detected by reporting false in a debounced handler. I suppose task queue could be full and let the debounced false value go through before a new scroll event is processed. In other words, before a new scrollTop value triggers the true branch (and resets the false, I guess, not sure how exactly merge works here).
Another finding: if the filter predicate passes, the invocation is throttled, which schedules a new task using setTimeout. Following the same logic of tasks scheduling in the event loop, the actual value of isScrolling could be different by the time the subscription callback is fired.
Attempted to make an illustration of how event loop might look.

To Reproduce Steps to reproduce the behavior:
- Go to the provided demo
- Scroll up
- Observe viewport jumping down against the scroll sometimes
It should be more noticeable if you try to accumulate more deviation by scrolling non-stop.
If you open devtools on the side, you could see that margin-top would reset to 0 sometimes during scrolling.
The amount of difference with each element could change depending on the first item, so sometimes refreshing could give a more janky collection.
<video src=https://user-images.githubusercontent.com/4955642/157632286-b8de2cee-29e1-4322-b2dc-4f351375a327.mp4></video>
Recorded on FF 98, Windows 11 ARM VM. Used touchpad for scrolling.
Btw applying a fix that only checks for isScrolling before publish, without correcting for available scrollTop, exposes accumulated margin-top if you happen to reach the top.
Expected behavior Compensation should be firing only after user truly stopped scrolling
Desktop (please complete the following information): Firefox, tested on v97+ on Windows.
Issue Analytics
- State:
- Created 2 years ago
- Comments:5 (4 by maintainers)

Top Related StackOverflow Question
Agreed on the
isScrolling- if that’s the root cause, that should be fixed. I will try to reproduce it. I will double-check on that, hopefully over the weekend.Breaking the problem further and based on your diagram, I tried to cause debounce leak due to heavy queue. Here’s a simplified test. Maybe I am missing something here, but it looks like the extra ops don’t affect it. But I might be too gentle.
Ultimately, if the rendered components can cause sufficient timer delay,
isScrollingis not the only flag that is going to get miscalculated. There are several other timers in the codebase. Here are a few ideas that can help: