2

I have a function that handles scrolls every period of time. This function works when users scroll with mouse wheel:

let shouldHandle = true
window.addEventListener('wheel', e => {
    if (shouldHandle) {
        handleScroll(e) // I will handle scrolls here
        shouldHandle = false
        setTimeout(() => {
            shouldHandle = true
        }, 750)
    }
})

However, when I am scrolling using my laptop's touchpad, scroll still happens even when I remove my finger (especially when I accelerate my finger enough and scroll, then immediately remove my finger from the touchpad). As a result, scroll still happens after the 750ms even when the users are not technically scrolling. This question has been asked here. The question did not receive an answer to handle this behavior.

I want to handle scroll only after a period of time has elapsed from the last scroll. The scrolls I want to handle must not be because of scrolling caused by this "predictive touch" scroll. Is there a way to achieve this as of now?

Richard
  • 7,037
  • 2
  • 23
  • 76

1 Answers1

3

The only way to handle that that comes to mind is to watch scrollY for a period of time (polling, perhaps every 50ms or so) after you've seen a scroll start and wait for it to stabilize (X milliseconds in the same position, for whatever value of X you decide on) and only then consider that scroll "completed" and start your 750ms timer. Constantly polling would be bad, but doing it for a brief period while the scrolling is still actively occurring seems acceptable.

Rough sketch (could probably use an overall timeout, for instance):

// VERY ROUGHLY
let shouldHandle = true
let lastScrollY = null
let lastScrollTimer = 0
window.addEventListener('wheel', e => {
    if (shouldHandle) {
        handleScroll(e) // I will handle scrolls here
        shouldHandle = false
        waitForScrollEnd(() => {
            setTimeout(() => {
                shouldHandle = true
            }, 750) // Or perhaps 700 on the basis that up to 50 was spent in `waitForScrollToEnd`
        })
    }
})
function waitForScrollEnd(cb) {
    clearTimeout(lastScrollTimer)
    lastScrollY = window.scrollY
    lastScrollTimer = setTimeout(poll, 50)

    function poll() {
        if (lastScrollY === window.scrollY) {
            lastScrollY = null
            lastScrollTimer = 0 // This is entirely optional but makes it parallel with the `else` below
            cb()
        } else {
            lastScrollY = window.scrollY
            lastScrollTimer = setTimeout(poll, 50)
        }
    }
}
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • I think that the code should have this line inserted before the last fourth line: `lastScrollY = window.scrollY`. Also, this code means that continuous scrolling not caused by "predictive touch" (instead because the user is really scrolling) not to be handled, right? – Richard Jan 08 '20 at 11:56
  • @Richard - I'm afraid I don't know what you mean by "the last fourth line. I'm fairly sure `lastScrollY = window.scrollY` isn't needed anywhere it isn't already in the above. Yes, `waitForScrollEnd` will wait for scrolling to end regardless of why it's ongoing. – T.J. Crowder Jan 08 '20 at 12:00
  • @Richard - (I don't think this relates to your comment.) Looking at the above with fresh eyes, it seems to me it wants to cancel a previous `waitForScrollEnd` if a new event comes in. I've added that, but it was really just a tweak anyway... – T.J. Crowder Jan 08 '20 at 12:02
  • Ah what I meant was: your code before only calls `waitForScrollEnd` once, which is when the user first scrolls. Afterwards, `waitForScrollEnd` will only update `lastScrollY` once, but calls `poll` every 50ms. However, as `lastScrollY` is only updated once, `poll` will continuously be called every 50ms even when the scroll has stopped. – Richard Jan 08 '20 at 12:50
  • @Richard - Doh!! Fixed. :-) (Fourth from last, I get it now.) – T.J. Crowder Jan 08 '20 at 12:58
  • Ah yes. Sorry, I meant `fourth from last` or `fourth to last`. Thank you for the answer. – Richard Jan 08 '20 at 13:18