11

I noticed mousewheel event is happening multiple times in mac osx. Can be atributed to inertia feature.

Is there a way to fix this behaviour?

(self signed ssl no worries please!) https://sandbox.idev.ge/roomshotel/html5_v2/

I'm using scrollSections.js https://github.com/guins/jQuery.scrollSections

And it uses mousewheel jquery plugin: https://github.com/brandonaaron/jquery-mousewheel

I'm seeing a lot of people having the same issue: https://github.com/brandonaaron/jquery-mousewheel/issues/36

There are some solutions but none works with scrollSections plugin.

Any ideas how to disable this inertia feature from JS?

My attempted fix:

// Fix for OSX inertia problem, jumping sections issue.
if (isMac) {

  var fireEvent;
  var newDelta = deltaY;

  if (oldDelta != null) {

    //check to see if they differ directions
    if (oldDelta < 0 && newDelta > 0) {
      fireEvent = true;
    }

    //check to see if they differ directions
    if (oldDelta > 0 && newDelta < 0) {
      fireEvent = true;
    }

    //check to see if they are the same direction
    if (oldDelta > 0 && newDelta > 0) {

      //check to see if the new is higher
      if (oldDelta < newDelta) {
        fireEvent = true;
      } else {
        fireEvent = false;
      }
    }

    //check to see if they are the same direction
    if (oldDelta < 0 && newDelta < 0) {

      //check to see if the new is lower
      if (oldDelta > newDelta) {
        fireEvent = true;
      } else {
        fireEvent = false;
      }
    }

  } else {

    fireEvent = true;

  }

  oldDelta = newDelta;

} else {

  fireEvent = true;

}

You can see fix implemented here: https://sandbox.idev.ge/roomshotel/html5_v2/ But it is a hit/miss.

Linus Juhlin
  • 1,175
  • 10
  • 31
  • Here's a similar question which seems to have been answered: http://stackoverflow.com/questions/17798091/osx-inertia-scrolling-causing-mousewheel-js-to-register-multiple-mousewheel-even – Sam Hanley Oct 14 '14 at 16:32
  • @sphanley debounce makes it even worse. I've tried the solution in that post and it doesn't really work. –  Oct 14 '14 at 17:00
  • Ok! Just thought it looked like it could be related. Good luck! – Sam Hanley Oct 14 '14 at 23:32
  • @sphanley it is definitely related, but solution is not even remotly acceptable. My solution works way better. –  Oct 16 '14 at 18:54
  • I would appreciate it if you could say whether the problem is solved or not and how useful were provided answers. In case there were not as precise as was required, you're encouraged to give us more detailed description. P.S. The webpage has superb design. Congrats! – Sam Braslavskiy Oct 20 '14 at 18:33
  • @SamBraslavskiy Thanks for the review :) I've been working on it for a month now, too many moving parts! I didn't have a chance to test out the solutions, I'm going to do it now. thx for reminding me. –  Oct 20 '14 at 20:00
  • @salivan I think I might have one more solution, which will work not only in osX, but in every browser that implements kinetic scrolling (with inertia). I'll think it over properly and post my solution here tomorrow. Hope the new approach will finally solve this problem ;-) – Sam Braslavskiy Oct 21 '14 at 18:54
  • @SamBraslavskiy we are all waiting for it :) – Alvaro Oct 21 '14 at 18:56
  • @salivan, I was finally able to test your solution in MacOS with a trackpad. It doesn't look bad. It seems to me that you are checking if the values of delta are increasing (or decreasing) so it will imply an acceleration, otherwise, you will ignore the scroll, as the user tends to decelerate when finishing to scroll. Which is the problem you are facing them? Where do you think your solution fails? I saw some weird behaviours in your example when reaching the last section,but the rest looks ok. – Alvaro Oct 21 '14 at 18:59
  • @SamBraslavskiy wow nice. We are waiting for it indeed! This problem is annoying as hell for this particular types of sites :) –  Oct 21 '14 at 19:06
  • @Alvaro yep exactly! Problem is when user uses very little scroll, my solutions fails to take into consideration that part. Sometimes scroll to top happens unexpectedly its hard to understand what is happening. –  Oct 21 '14 at 19:06
  • @salivan what's exactly the problem when they don't scroll enough? Are you still talking about Mac users? – Alvaro Oct 21 '14 at 19:32
  • @Alvaro when they don't scroll enough scroll doesn't stop on the next section, but continues to the next one. Yes mac users. –  Oct 22 '14 at 06:37

4 Answers4

13

The latest solution with timeouts had one major drawback: kinetic scrolling effect could last rather long (even 1s or so)... and disabling scrolling for 1-2 seconds wouldn't be the best decision.

Soooo, as promised, here's another approach.

Our goal is to provide one response for one user action, which in this case is scrolling.

What's 'one scrolling'? For the sake of solving this problem, let's say that 'one scrolling' is an event that lasts from the moment the page has started to move till the moment the movement has ended.

Kinetic scrolling effect is achieved by moving the page many times (say, every 20ms) for a small distance. It means that our kinetic scrolling consists of many-many little linear 'scrollings'.

Empirical testing has showed that this little 'scrollings' happen every 17-18ms in the middle of kinetic scroll, and about 80-90ms at the beginning and the end. Here's a simple test we can set up to see that:

var oldD;
var f = function(){
    var d = new Date().getTime();
    if(typeof oldD !== 'undefined')
        console.log(d-oldD);
    oldD = d;
}
window.onscroll=f;

Important! Every time this mini-scroll happens, scroll event is triggered. So:

 window.onscroll = function(){console.log("i'm scrolling!")};

will be fired 15 to 20+ times during one kinetic scroll. BTW, onscroll has really good browser support (see compatibility table), so we can rely on it (except for touch devices, I'll cover this issue a bit later);

Some may say that redefining window.onscroll is not the best way to set event listeners. Yes, you're encouraged to use

 $(window).on('scroll',function(){...});

or whatever you like, it's not the point of the problem (I personally use my self-written library).

So, with the help of onscroll event we can reliably say whether this particular mini-movement of the page belongs to one long-lasting kinetic scroll, or is it a new one:

    var prevTime = new Date().getTime();
    var f = function(){
        var curTime = new Date().getTime();
        if(typeof prevTime !== 'undefined'){
            var timeDiff = curTime-prevTime;
            if(timeDiff>200)
                console.log('New kinetic scroll has started!');
        }
        prevTime = curTime;
    }
    window.onscroll=f;

Instead of "console.log" you can call your desired callback function (or event handler) and you're done! The function will be fired only once on every kinetic or simple scroll, which was our goal.

You may have noticed that I've used 200ms as a criteria of whether it's a new scroll or a part of the previous scroll. It's up to you to set it to greater values to be 999% sure you prevent any extra calls. However, please keep in mind that it's NOT what we have used in my previous answer. It's just a period of time between any two page movements (whether it's a new scroll or a little part of a kinetic scroll). To my mind, there's a very little chance that there will be a lag more than 200ms between steps in kinetic scroll (otherwise it will be not smooth at all).

As I've mentioned above, the onscroll event works differently on touch devices. It won't fire during every little step of kinetic scroll. But it will fire when the movement of the page has finally ended. Moreover, there's ontouchmove event... So, it's not a big deal. If necessary, I can provide solution for touch devices too.

P.S. I understand that I've written a bit too much, so I'd be happy to answer all your questions and provide further code if you need one.

Provided solution is supported in all browsers, very lightweight and, what's more important, is suitable not only for macs, but for every device that might implement kinetic scrolling, so I think it's really a way to go.

Sam Braslavskiy
  • 1,268
  • 10
  • 19
  • You can set the initial value of prevTime = 0 as well. Does not make a big difference in this case. – Sam Braslavskiy Oct 23 '14 at 05:37
  • Its a good solution, but still scrolling when the user scrolls slower at the end. What about a combination between the solution proposed @salivan taking in care acceleration max points? Once the acceleration doesn't overcome again the maximum it shouldn't scroll. – Alvaro Oct 23 '14 at 23:40
  • @SamBraslavskiy works great in my case, I went with 50ms, I'm not sure if that's a safe bet for all mousewheel implementations :S Anyhow, touch part is crazy and doesn't really work for me, https://sandbox.idev.ge/roomshotel/html5_v3/ can you please take a look? It is inside scrollsections plugin, section which deals with touch. –  Oct 29 '14 at 08:11
  • @salivan well, yeah, It looks pretty messy on iPad. What I'd recommend is to read [this thread](http://stackoverflow.com/questions/2863547/javascript-scroll-event-for-iphone-ipad) if you haven't already (especially the answers of geo1701 and Dave Mackintosh). Try their solutions first. About these 50ms... I've tested it on my mac with the latest chrome and safary, works with no mistakes, but I still think that 100ms is the safest value. – Sam Braslavskiy Nov 03 '14 at 06:41
  • @salivan I really didn't want to say that, but at the end of the day it seems to me a better idea to leave native user scrolling... I perfectly understand that it's a tough decision, but I strongly believe it would provide better user experience. If someone would like to visit specific section of the site, he would click corresp. menu item on the left. And if he scrolls, it should be native (without fixation). You will still have to track how far has the user scrolled to trigger animation and other actions, but it's not a great problem (every parallax site solves it). Think about it... – Sam Braslavskiy Nov 03 '14 at 06:53
4

You know, I think it's a better idea to use timeouts in this case. Why not write something like this:

// Let's say it's a global context or whatever...:
var fireEvent = true;
var newDelta, oldDelta, eventTimeout;
newDelta = oldDelta = eventTimeout = null;

// ... and the function below fires onmousewheel or anything similar:
function someFunc(){
    if(!fireEvent) return; // if fireEvent is not allowed => stop execution here ('return' keyword stops execution of the function), else, execute code below:
    newDelta = deltaY;
    if(oldDelta!=null&&oldDelta*newDelta>0){ // (1.1) if it's not the first event and directions are the same => prevent possible dublicates for further 50ms:
        fireEvent = false;
        clearTimeout(eventTimeout); // clear previous timeouts. Important!
        eventTimeout = setTimeout(function(){fireEvent = true},500);
    }
    oldDelta = newDelta;
    someEventCallback(); // (1.2) fire further functions...
    }

So, any mousewheel event fired within half a second after any previous mousewheel event call will be ignored, if it is made in the same direction as previous (see condition at 1.1). It will solve the problem and there's no way user would spot this. Delay amount may be changed to better meet your needs.

The solution is made on pure JS. You're welcome to ask any questions about integrating it in your environment, but then I'll need you to provide further code of your page.

P.S. I have not seen anything similar to eventCallback() call in your code (see 1.2 of my solution). there was only fireEvent flag. Were you doing something like:

if(fireEvent)
    someEventCallback();

later on or something?

P.P.S.note that fireEvent should be in global scope in order to work here with setTimeout. If it's not, it's also quite easy to make it work fine, but the code needs to be altered a bit. If it's your case, tell me and I'll fix it for you.

UPDATE

After a brief search I found out, that similar mechanism is used in Underscore's _debounce() function. See Underscore documentation here

Sam Braslavskiy
  • 1,268
  • 10
  • 19
  • Please note, that I've added "clearTimeout" line just before setTimeout in my answer. This is important, while it prevents function from firing multiple times. ClearTimeout won't throw any errors/warnings even if there's no Timeout to clear, which is fine in this case. – Sam Braslavskiy Oct 21 '14 at 08:49
  • Are you on mac btw? it would help if you could see what I'm talking about :) –  Oct 21 '14 at 09:29
  • @salivan he is suggesting exactly the same thing which was implemented in fullPage.js. And I also believe that's the only way to deal with it. It still not being perfect, as Mac has a strong inertia which could bypass that timeout of 500 or even 600 milliseconds if scrolled very fast, but it solves the problem for normal scrolls on a Mac. – Alvaro Oct 21 '14 at 09:54
  • @Alvaro with your plugin I do notice timeout kicking in sometimes. My solution actually avoids this problem and user doesn't feel it. –  Oct 21 '14 at 12:00
  • @salivan not sure what do you mean with *I do notice timeout kicking in sometimes*. In any case, you don't have to use `timeOut`, you could use a timer instead, as [pagePiling.js](https://github.com/alvarotrigo/pagePiling.js/blob/master/jquery.pagepiling.js#L371) is doing or even [one-page-scroll](https://github.com/peachananr/onepage-scroll/blob/master/jquery.onepage-scroll.js#L264). I'm not quite sure how your solution works yet, I'll test it this afternoon. – Alvaro Oct 21 '14 at 12:18
  • @Alvaro what I mean is that, If I slide down I fast to second section, I can feel when scroll is disabled. My solution is based on mousewheel plugin dependency of scrollsections plugin, are you using that too? –  Oct 21 '14 at 13:30
  • Not sure what you mean with *I can feel when scroll is disabled*. There's a delay until you can scroll again. Is that what you mean? And no, its not using the mouse-wheel plugin. – Alvaro Oct 21 '14 at 13:57
  • @Alvaro yep that's exactly what I feel the delay. I might be picking on small details here, but it is noticable for me. –  Oct 21 '14 at 19:07
  • well at least you go the poinst anyways :) –  Oct 22 '14 at 18:51
  • @salivan Maybe it's because I was a little bit late... The bounty was limited in time. After the time ran out, as I suppose, the answer with the most upvotes received a half of the points. Bad luck... but they're just points anyway))) so never mind) I would be happy if my solution helped you. Could you please let me know whether it was useful or not? – Sam Braslavskiy Oct 23 '14 at 03:11
1

Have you though about using fullpage.js instead? It has a delay between arriving to a section and the moment you are able to scroll to the next section which solves part of the problem Mac users experience with track-pads or Apple magic mouses.

It would also provide you some other benefits, such as much more options, methods and compatibility with touch devices and old browsers with no CSS3 support.

Alvaro
  • 40,778
  • 30
  • 164
  • 336
  • I've considered this option too. I saw this plugin during initial research. But it seemed jaggy to me, not smooth! But I realised it is appending of #urls that cause the slow down, I had similar issues before. I disabled #urls and it is butter! –  Oct 16 '14 at 18:55
  • @salivan are you using it with the option `css3:true`? It is set to false by default. That might be the reason. It should be as fast as any other as CSS3 is quite smooth. You can edit the easing if you want as well. – Alvaro Oct 17 '14 at 09:11
  • @salivan if you feel it jaggy somehow, open an issue in the repository forum so it can be reviewed: https://github.com/alvarotrigo/fullPage.js/issues – Alvaro Oct 17 '14 at 09:13
  • sorry, but on mac performance is not very good. css3 doesn't help either. I will open issue when I have time to test your plugin well. –  Oct 21 '14 at 09:28
  • @salivan no one else have reported that problem before, and I have a Mac at home in which it works as expected. You might be using very big images or something else that makes it laggy. [The demo site](http://alvarotrigo.com/fullPage/) works well for me. – Alvaro Oct 21 '14 at 09:32
  • @salivan yeah, as I expected, I've analyze [your site](https://sandbox.idev.ge/roomshotel/html5_v2/) and it seems to me that you are loading an insane amount of data in it. (almost 22Mb!!!). You are loading images of 1.5Mb which could probably be reduced to 200 - 300Kb. The bigger the images are, the more difficult it would be for the plugin to moves them. – Alvaro Oct 21 '14 at 09:47
  • @Alvero true, but scrollsections seems to handle big images better. Images are optimized and are in the area of 500mb max. Since I'm using canvas gpu is used and performance is further improved. –  Oct 21 '14 at 11:59
0

To have something to start with, let's make your solution shorter (therefore easier to understand & debug):

var fireEvent;
var newDelta = deltaY;
var oldDelta = null;

fireEvent = EventCheck();
oldDelta = newDelta;

function EventCheck(){
    if(oldDelta==null) return true; //(1.1)
    if(oldDelta*newDelta < 0) return true; // (1.2) if directions differ => fire event
    if(Math.abs(newDelta)<Math.abs(oldDelta)) return true; // (1.3) if oldDelta exceeds newDelta in absolute values => fire event
    return false; // (1.4) else => don't fire;
}

As you see, it does absolutely what your code does. However, I can't understand this part of your code (which corresponds to (1.3) in my snippet):

 //check to see if the new is lower
 if (oldDelta > newDelta) {
    fireEvent = true;
 } else {
    fireEvent = false;
 }

from code provided it's unclear how deltaY is calculated. As one could assume, delta equals to endPosition - initialPosition. So, oldDelta>newDelta does not mean that the new position is lower, but that the new gap between these two values is bigger. If it's what it mean and you still use it, I suppose you try to track inertia with that. Then you should alter comparative operator (use less than, instead of greater then and vice-versa). In other words, I'd write:

if(Math.abs(newDelta)>Math.abs(oldDelta)) return true; // (1.3) 

you see, now I've used 'greater than' operator, which means: newDelta exceeds oldDelta in absolute values => it's not inertia and you can still fire the event.

Is it what you're trying to achieve or have I misinterpreted your code? If so, please explain how deltaY is calculated and what was your goal by comparing old&new Deltas. P.S. I'd suggest not to use if(isMac) in this step, while a problem can also potentially hide there.

Sam Braslavskiy
  • 1,268
  • 10
  • 19
  • Well this problem is mac related, on windows it works just fine :( anyhow, I don't know how Delta is calculated, this is something mousewheel js plugin provides. –  Oct 21 '14 at 09:31
  • @salivan are you sure its a Mac only problem? Have you tested it in windows laptops track-pads? And on mouses with a continuous scroll wheel? What about touch devices or touch screens? – Alvaro Oct 21 '14 at 09:35
  • @Alvaro yes, mac has the most hectic inertia implementation. Windows mouses are calmer. –  Oct 21 '14 at 12:01
  • Hey, could you please take a look at this? http://stackoverflow.com/questions/26798580/fighting-touch-events-on-mobile-devices-highjacking-normal-scroll –  Nov 16 '14 at 08:21