Search code examples
javascripthtmlscroll

perform action when the user scrolls, before inertia/momentum scrolling takes over


I want a function to run almost instantaneously as soon as the user stops scrolling, without waiting for inertia scrolling to finish. I have checked several answers like this one: force-stop momentum scrolling on iphone/ipad in javascript and this: Disable inertia scroll for "single-page" webapp, but I need to use the wheel event since mousewheel is deprecated and e.preventdefault() doesn't answer my question. This answer: How do I know when I've stopped scrolling? also doesn't consider inertia scrolling and the event fires a few seconds after the user has stopped scrolling.


Solution

  • The approach I came up with suits my needs and I couldnt find any other solution on the net so I'll answer this question Q&A style, if a better approach is used to answer this question, ill be more than happy to mark that as the answer until then, ill keep this open for a few days.

    I was testing it out and I saw event.deltaY to increase till a max value and when the user stops scrolling, it starts decreasing BUT thats not always the case, since from my understanding deltaY is the change in Y from the last event was fired, so a code like this

     mainElement!.addEventListener("wheel", (ev) => {
            ev.preventDefault(); // stop scrolling
            const absEvVal = Math.abs(ev.deltaY);
            console.log(ev.deltaY);
            if (absEvVal > max_abs_ev_val) {
                max_abs_ev_val = absEvVal;
                switchy = true;
            } else {
                if (switchy) {
                    console.log("max val", max_abs_ev_val);
                    console.log("do function now");
                    switchy = false;
                }
            }
    })
    

    can produce inconsistent results like this (albeit rarely) showing result of above code ^ Here I only scrolled once.

    So I combined the timeout function with my code and it works perfectly,

    
        let timerTimeout: any;
        let switchy = true;
        let performLogic = true;
        let abslastDeltaY = 0;
    
    
        mainElement!.addEventListener("wheel", (ev) => {
            ev.preventDefault(); // stop scrolling
            const absEvVal = Math.abs(ev.deltaY);
            if (absEvVal > 100) { // a threshold value, just in case the user accidentally scrolls a bit, dont want to run the code needlessly
                if (absEvVal > abslastDeltaY) {
                    switchy = true;
                } else if (absEvVal < abslastDeltaY && switchy) { //this code will run just before the abs(ev.deltaY) starts decreasing
        
                    clearTimeout(timerTimeout);
                    if (performLogic) {
                        console.log("do logic");
                        performLogic = false;
                    }
    
                    timerTimeout = setTimeout(() => {
                        switchy = false;
                        performLogic = true;
                    }, 100);
                }
                abslastDeltaY = Math.abs(ev.deltaY);
            }
        })
    

    Now in my code here, note that I am performing the logic BEFORE setTimeout, this would mean that my code will run before the user has finished scrolling , in those rare cases where the first code fails, but this will be very close to when the user finishes scrolling so its alright for me. If you dont mind a bit of delay (the one you provide to your timeout) , then you can put the code in the timeout rather than before it , like so

      ...
                    clearTimeout(timerTimeout);
                    timerTimeout = setTimeout(() => {
                         if (performLogic) {
                             console.log("do logic");
                             performLogic = false;
                        }
                        switchy = false;
                        performLogic = true;
                    }, 100);
        ...
    

    I've tested this on Chrome and Safari and it works properly.