Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

osx inertia #36

Closed
annam opened this issue Aug 14, 2012 · 69 comments
Closed

osx inertia #36

annam opened this issue Aug 14, 2012 · 69 comments
Labels

Comments

@annam
Copy link

annam commented Aug 14, 2012

hi!

when using a trackpad or a magic mouse multiple events are triggered for every touch-scroll, sometimes taking as long as 1-2 seconds to stop, depending the scrolling "speed" and due to the inertia setting on mac osx.

I'm using mousewheel in a scenario where I move through news items on mousewheel and ideally I only want to move by on one each mousewheel action on windows, one each touch-scroll gesture on a mac trackpad.

The multiple events triggered make this impossible to do. I've tried unbinding and rebinding but as soon as I rebind the events triggered by the touch action before the unbind keep getting detected. I've tried filtering the delta to only scroll if it's between 2 values but its making the scroll very unreliable. Rebinding mousewheel only after the inertia events stop is also an impossibility because it would mean only allowing one scroll every two seconds.

do you have any ideas on how to overcome this?

@danro
Copy link

danro commented Aug 27, 2012

Underscore's debounce function is a good solution here.
http://documentcloud.github.com/underscore/#debounce

You would set up your callback passing the 'immediate' parameter like this:
myCallback = _.debounce(myCallback, 200, true);

@riccardolardi
Copy link

I was having the exact same problem here. I've been trying to manipulate/lower the delta (by multiplying it with some negative threshold value) to achieve some "slower" scrolling and to deal with osx scroll inertia. Now looking into underscore's (actually using lo-dash.js) debounce function I was able to make the scrolling more controllable. Thanks for pointing that out!

@yoannmoinet
Copy link

I found out that the magic number here was 40.
Let me explain myself.
It seems that with the trackpad (probably with the magic mouse too) the delta get increased 40 times.
And it stays that way even if you take back your normal mouse and scroll with it later.
So what I did, simply :

b.mousewheel(function(evt,delta,deltaX,deltaY){
    if(Math.abs(deltaY)>=40)
        deltaY/=40;
    if(Math.abs(deltaX)>=40)
        deltaX/=40;

    //Do your stuff here.
});

@DigitalWheelie
Copy link

Any recent, all-encompassing solution w/ the latest version, that will work when the Apple Magic Mouse is present, but also if not, on Windows, tablets and phones (iOS and otherwise)?

Some of these ideas seem to help a bit, or in certain circumstances, but then fall apart on others. Can't seem to find a pattern...

Although has some interesting possibilities:
http://stackoverflow.com/questions/17798091/osx-inertia-scrolling-causing-mousewheel-js-to-register-multiple-mousewheel-even

Yet also, not quite THE answer.

@brandonaaron
Copy link
Contributor

The issue @neekgerd brought up is actually related to #66.

@ghost ghost assigned brandonaaron Oct 25, 2013
@brandonaaron
Copy link
Contributor

I added some functionality that can help with this issue in the 3.2.x branch.

$(...).on('mousewheel', { mousewheel: { behavior: 'throttle', delay: 100 } }, function(event) { ... });

or

$(...).on('mousewheel', { mousewheel: { behavior: 'debounce', delay: 100 } }, function(event) { ... });

@brandonaaron
Copy link
Contributor

I went ahead and bumped this to a 4.0.x branch instead of the previously mentioned 3.2.x branch because the level of changes are pretty extreme.

@brandonaaron
Copy link
Contributor

In experimenting with this feature I noticed that it might be desirable to prevent the default behavior for all the mouse wheel events that are essentially ignored due to the throttling. In addition I was thinking about utilizing this concept of putting the settings in the data param for a few other nice-to-have features as well... So I think I might change up the setting names a little based on how that work goes.

Also thinking about alternative ways to add this behavior. Possibly just expose a factory-style method that can build the desired delayed handler.

@brandonaaron
Copy link
Contributor

Another update on this. You can now pass the following as settings for the mousewheel event:

{ mousewheel: { debounce: { delay: 200 } }
{ mousewheel: { throttle: { delay: 200 } }

Or you can roll with the defaults by just setting to true like this:

{ mousewheel: { debounce: true } }
{ mousewheel: { throttle: true } }

Be default it will not prevent the default nor stop the propagation but you can do both like this:

{ mousewheel: { debounce: { preventDefault: true } }
{ mousewheel: { throttle: { preventDefault: true } }

There is another feature that I added as well... intent. It work similar to the popular hoverIntent plugin.

{ mousewheel: { intent: { delay: 300, sensitivity: 7 } }

Or use the defaults and just pass true:

{ mousewheel: { intent: true } }

You can even use both the intent feature and the throttle/debounce but only preventDefault once the user has expressed intent to use the mousewheel on that particular object.

{ mousewheel: { intent: true, throttle: { preventDefault: true } }

I'm still doing lots of testing and exploring of these features/ideas and they very well could change again. All of this work is happening in the 4.0.x branch.

@brandonaaron
Copy link
Contributor

I just landed leading, trailing, and maxDelay options for throttle/debounce settings in the 4.0.x branch. The maxDelay setting only applies to debounce. By default leading is false and trailing is true for debounce while both leading and trailing are true for throttle.

@mikaa123
Copy link

awesome man. keep up the good work.

@jashwant
Copy link

jashwant commented Dec 7, 2013

Great update on this @brandonaaron . debounce stops the execution for given seconds. But what does the throttle do then ? Can you create a simple demo, where I can see how I can use those and test ?

@miguelpeixe
Copy link

@jashwant throttle caches the event action and do one at a time, by the delay you specified. debounce doesn't cache, executes only 1 and throw away the subsequent actions until the next release (timeout of the delay specified)

@miguelpeixe
Copy link

I'm still having issues controlling macosx mousewheel event. I can't use debounce feature because it waits the specified ms to start, I need it to start immediatelly and only allow to scroll again after some time.

I'm able to do that normally on Windows and Linux (any browser) with my own code. On MacOSX feels erratic, some times it works, but with a "strong" mousewheel, it executes the event again before the time. No idea why, how or how to stop it.

Here's a link

Here's the code:

var enableRun = true;

var runSections = function(delta) {

    if(enableRun) {

        if(delta < 0) {
            // up
            self.sly.prev();
        } else if(delta > 0) {
            // down
            self.sly.next();
        }

        enableRun = false;

        setTimeout(function() {
            enableRun = true;
        }, 800);

    }

};

var homeScroll = function(event) {

    var delta = event.deltaY;

    if(self.sly.initialized) {

        var items = self.sly.items;
        var current = self.sly.rel;
        var isLastItem = (items.length - 1 === current.lastItem);

        if(isLastItem) {

            if(delta < 0) {

                runSections(delta);

                event.stopPropagation();
                event.preventDefault();

            }

            if(!enableRun) {
                event.stopPropagation();
                event.preventDefault();
            }

        } else {

            runSections(delta);

            event.stopPropagation();
            event.preventDefault();

        }

    }

};

$(window).on('mousewheel', homeScroll);

@brandonaaron
Copy link
Contributor

@miguelpeixe you can turn on a leading debounce behavior. By default leading is false and trailing is true for debounce. There is also a maxDelay option for the debounce behavior.

$(...).on('mousewheel', { mousewheel: { debounce: { leading: true, trailing: false, maxDelay: 500 } }, fn);

Related code for the leading/trailing default settings: https://github.com/brandonaaron/jquery-mousewheel/blob/4.0.x/jquery.mousewheel.js#L206-L207

@miguelpeixe
Copy link

Thanks @brandonaaron, what does maxDelay do?

@brandonaaron
Copy link
Contributor

If the event is debounced for maxDelay milliseconds it will force a fire of the event.

@brandonaaron
Copy link
Contributor

@miguelpeixe Did the additional settings for debounce work for your particular use-case?

@miguelpeixe
Copy link

@brandonaaron actually no, my client decided to disable our custom wheel feature but I'm still very interested in solving this out. Such a simple thing, causing so much stress, that's weird.

Do you know what I'm trying to do? if you do and want to solve this, would be nice to have some help!

In my case, I don't really want to force a wheel event, I just need to control it to happen only on the given ms (if the user wants to, with leading turned on). I couldn't manage to do that with the available settings. The closest I got was with the code I previously posted here (worked on every os and browser, except macosx).

@fenicento
Copy link

Hi, I'm having the same issue of miguelpeixe. I can control events on all browsers on windows; on mac Os x I'm using a magic mouse or a trackpad: on firefox everything goes fine, while on chrome (espcially with strong wheel actions) things go out of control and preventDefault seems to be ignored... the code is pretty similar to miguelpeixe's one, I can post it if you think it can be useful...

@edeesims1
Copy link

I currently have the following

$(document).on('mousewheel', { mousewheel:{ debounce: { leading: true, trailing: false, preventDefault: true } } }, function(e) {

But when I scroll with a trackpad, I am still getting 3 events firing. I am trying to use this to scroll to specific positions on my page. Works great with a mouse but not with a trackpad.

Any suggestions?

@brandonaaron
Copy link
Contributor

@edeesims1 The default delay is only 100ms. Try tweaking the delay to see if that gets you a better result.

@marcbelletre
Copy link

Hi everyone,

I went here a few times ago to find a solution and I finally found one by myself. I wanted to share it with you so here it is.
Everytime the event is fired, I check if the previous event was fired less than 100ms before and if the event were fired less than 10 times. Else, I don't allow scrolling so the event can be automatically fired only 10 times by the browser. If the difference between the two times is greater than 100ms, we can conclude that the event was fired by the user, so we can allow scrolling.

function mouseHandle(event) {
    newDate = new Date();
    var scrollAllowed = true;

    if( wheel < 10 && (newDate.getTime()-oldDate.getTime()) < 100 ) {
        scrollPos -= event.deltaY*(10-wheel);
        wheel++;
    }
    else {
        if( (newDate.getTime()-oldDate.getTime()) > 100 ) {
            wheel = 0;
            scrollPos -= event.deltaY*60;
        }
        else {
            scrollAllowed = false;
        }
    }

    oldDate = new Date();

    if( scrollAllowed ) {
        // do your stuff here
    }
}

I hope this could help. In my case it seems to work pretty good !

@thSoft
Copy link

thSoft commented Mar 15, 2014

Wow, @Prettyletter, your workaround works perfectly! Thank you! @brandonaaron, have you considered incorporating it to the code?

@jcsuzanne
Copy link

+100!!!! Finally ;)

In the "debounce" function way, i update the script like this

var killbounce = function(_func, _wait)
{
var
wheel = 0
, oldDate = new Date()
, scrollPos = 0
;
return function() {

        var
            context         =   this
        ,   args            =   arguments
        ,   event           =   args[0]
        ,   getDeltaY       =   event.deltaY
        ,   newDate         =   new Date()
        ,   newTime         =   newDate.getTime()
        ,   oldTime         =   oldDate.getTime()
        ,   scrollAllowed   =   true
        ;

        if( wheel < 10 && (newTime-oldTime) < _wait ) {
            scrollPos -= getDeltaY*(10-wheel);
            wheel++;
        }
        else {
            if( (newTime-oldTime) > _wait ) {
                wheel = 0;
                scrollPos -= getDeltaY*60;
            }
            else {
                scrollAllowed = false;
            }
        }
        oldDate = new Date();
        if( scrollAllowed ) {
            _func.apply(context, args);
        }
    }
};

sorry for the trash display...

@brandonaaron
Copy link
Contributor

Sorry I haven't had the time to dig into this some more. Looks promising. Might try to incorporate this into the next major release with the idea of the settings so that it can be configurable. I don't think I'll have time until next month though.

@gstaruk
Copy link

gstaruk commented May 2, 2014

Hi there, just wondering when the new version of the plugin is due for release?

@majorwaves
Copy link

I think I'm trying to achieve the same things as @Prettyletter and @jcsuzanne, but I'm having trouble putting in their code. Could someone hold my hand and tell me how they implemented that in relation to the '$(document).on('mousewheel'…' line.

@d4nyll
Copy link

d4nyll commented May 6, 2015

I took some inspiration from @msimpson and created lethargy, that helps you detect when an event is fired from inertial scroll. You can then use this information to decide whether to run a function or not.

Lethargy works primarily by picking up when the wheelDelta values are decreasing, as you'll observe towards the end of the inertial scroll.

Please do check it out and give me feedback!

@f0rmat1k
Copy link

Guys, we have written the plugin that solves inertial problems. Check it out

@martinbroos
Copy link

Any news on this bug? I tried Lethargy but that didn't work for me.
For now i did a simple check on mac users and lowered the delta so it won't be too enoying for users.

        $("html, body").on('mousewheel', function (event, delta) {
            event.preventDefault();
            event.stopPropagation();

            // temp solution lower scroll speed on mac to minimize Inertia Scrolling issue
            var isMac = /mac/i.test(navigator.platform);

            if (isMac) {
                this.scrollLeft -= (delta * 2);
            } else {
                this.scrollLeft -= (delta * 30);
            }
        });

The strange thing is that an older version (3.0.6) of this plugin does work. See https://css-tricks.com/examples/HorzScrolling/

But this version probably has some other cross browser issues so I rather not go back.

@Stiropor
Copy link

@f0rmat1k You plugin works excellent! Thanks

@dominicfallows
Copy link

The plugin (https://github.com/promo/wheel-indicator) from @f0rmat1k works great for my needs also (vertical stacked website, scroll locking to each new section)

@Yura-ka
Copy link

Yura-ka commented Feb 9, 2016

My solution to the problem scroll mac osx

$(function() {
    var $domElem = $('#id'); // dom element(or group) which should be filtered event
    var stopScrollfixMac = false;
    var scrollSumPositive = 0;
    var scrollSumNegative = 0;
    $domElem.on("mousewheel", function (event, 
        if (event.originalEvent.wheelDelta < 0) {
            scrollSumPositive = +event.originalEvent.wheelDelta;
        } else {
            scrollSumNegative = +event.originalEvent.wheelDelta;
        }
        if (scrollSumPositive <= -120 || scrollSumNegative >= 120) {
            scrollSumPositive = 0;
            scrollSumNegative = 0;
            stopScrollfixMac = false;
        } else {
            stopScrollfixMac = true;
        }
        if (stopScrollfixMac) {
            event.preventDefault();
            event.stopPropagation();
            return false;
        } 
    });

});

P.S.
in ms windows for one scroll event.originalEvent.wheelDelta = 120/-120

@colindcampbell
Copy link

I'm not sure how, but the smart folks at google figured out a way to address this issue. Check out http://www.google.com/inbox/

If you look at the source it appears that they have some fancy math going on. I can't decipher it but someone more experienced might be able to. Here is the minified version of the functions involved:

function qg() { kg.call(this); this.Sa = 400; this.S = 200; this.Ra = 0; this.fe = !0; this.Ea = !1; this.ld = Z.Zh || !1; this.va = this.$a = null ; this.Va = 0; this.oc = Z.viewport.width; this.Od = 0; this.Pd = 200; this.Hb = this.gg = this.Ee = 0; this.di = 40; if (this.ld) { var a = document.createElement("div"); W(a, "graph-swipe"); document.body.appendChild(a); this.$a = document.createElement("canvas"); a.appendChild(this.$a); this.xf = document.createElement("div"); a.appendChild(this.xf); this.$a.width = this.oc; this.$a.height = this.Sa; this.va = this.$a.getContext("2d"); this.va.lineWidth = 4 } } B(qg, kg); g = qg.prototype; g.aa = function() { qg.m.aa.call(this); this.Ee = Z.da.now(); requestAnimationFrame(x(this.eg, this)) } ; g.Rb = function() { qg.m.Rb.call(this); this.g.f(this.A, "wheel", this.Sf); this.g.f(this.A, "mousewheel", this.Sf) } ; g.Sf = function(a) { a = a.oa; var b = 0; a.wheelDelta && (b = a.wheelDelta / 120); a.detail && (b = -a.detail / 3); a.deltaY && (b = -a.deltaY / 10); void 0 !== a.wheelDeltaY && (b = a.wheelDeltaY / 120); rg(this, b); a.stopPropagation && a.stopPropagation(); a.cancelBubble = !0; a.preventDefault && a.preventDefault(); return a.returnValue = !1 } ; function rg(a, b) { var c = .5 * a.Sa + 30 * b , d = !1; c > a.Sa && (d = !0, c = a.Sa); 0 > c && (d = !0, c = 0); d && a.Ea && (a.Hb += 5); var e = Math.abs(b) > (a.fe ? .01 : .25) , f = Math.abs(b) > Math.abs(a.Ra) , d = 0 > b && c < a.S || 0 < b && c > a.S; 1 === Math.abs(b) && (f = .5 >= Math.abs(a.Ra)); if (e = !a.Ea && e && d && f) a.Ra = b, a.Ea = !0, a.Hb = a.di, clearTimeout(a.gg), a.gg = setTimeout(x(function() { this.Ea = !1 }, a), 1E3), 0 > b ? sg(a) : tg(a); d && (a.S = c); a.Ea ? Math.abs(b) > Math.abs(a.Ra) && (a.Ra = b) : a.Ra = b; a.ld && ug(a, c, e) }

@bercikboris
Copy link

please is there any WORKING solution for OS X ?

@davidroffe
Copy link

Just wanted to chime in and add I had a similar problem to @dominicfallows and @f0rmat1k's solution worked great!

@permanyer
Copy link

@f0rmat1k's solution worked great

+1

@thecheshirecat
Copy link

thecheshirecat commented Mar 23, 2017

I'm having troubles with MAC mouses/trackpads too. The thing is that the inertia stills there, and I need to get rid of it if possible.

$("body").on("mousewheel", function(e) {
    var body = $(this);
    //If the body doesn't have the "animating" class, we add it and do the animations here,
    //If it has it, it doesn't does anything until it finishes animating
    if(!body.hasClass("animating")) {
        body.addClass("animating");
        //MY CODE GOES HERE
        ...
        setTimeout(function() {
            body.removeClass("animating");
        }, 1500);
    }
});

That's my code, but the inertia makes the scrolling again after the 1500ms of the first scroll.

Is this issue fixed and I'm missing something?

PS: i used the latest version of the plug-in

@anojht
Copy link

anojht commented Apr 12, 2017

Im not sure if this helps but I had scrolling issues on mac whereby scroll event was being fired more than once and causing pages to scroll more than intended. So eventually we implemented the following it is working on mac without causing issues on other platforms: cubetransition

The code above detects scroll events over duration of time and determines if being fired by a mouse or a track pad then gives you the option to deal with it accordingly. If it is a track pad then it disable extra scroll behavior with a timeout function.

@sebringj
Copy link

The delta starts slower then goes to a peak, then goes slower again. If continual scrolling, will have this bouncy affect in the data as a user flicks their finger having peaks and troughs of delta values. Code has to address this, not looking at it like a single flat line. I'll see if I have the time.

@anojht
Copy link

anojht commented Nov 12, 2017

@sebringj Your observed behavior is correct. Please see if the following fiddles help speed up the solution process:

jsfiddle1
jsfiddle2

@sebringj
Copy link

@anojht thanks i made something similar with storing an array checking if > 3 size then check if middle is greater than adjacent code is small and discards array when peak detected and fires callback I’ll post when i get back.

@sebringj
Copy link

well you can see source here https://s3.amazonaws.com/jasonsebring.com/mynticoin/index.html see main.js

@anojht
Copy link

anojht commented Nov 13, 2017

@sebringj Just checked it out, scrolling is broken on Chrome on Windows 10 when using a mouse with a mouse wheel. It does not trigger the page scroll sometimes. Also, that's a beautiful website!

@dmcshehan
Copy link

Underscore's debounce function is a good solution here.
http://documentcloud.github.com/underscore/#debounce

You would set up your callback passing the 'immediate' parameter like this:
myCallback = _.debounce(myCallback, 200, true);

This worked for me.

@szfdiwang
Copy link

Guys, we have written the plugin that solves inertial problems. Check it out

3Q for the plugin ! nice tools !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests