21

Is there a way to disable or detect that wheel events are from the "inertia" setting on a Mac?

I'd like to be able to tell the difference between real events and the others...or disable that kind of scrolling for a particular page.

mherf
  • 311
  • 1
  • 2
  • 4
  • 9
    I don't have a real answer, so just commenting... but I would guess you can't tell the difference. The device driver / system software is feeding these events to you and probably not telling you how they were generated. Also, if I turned that on I probably want it on and won't appreciate you disabling it. – Stephen P Dec 02 '10 at 20:29

8 Answers8

5

I found a solution that works really well for this. Below is some pasted code from my project. It basically comes down to this logic:

A scroll event is from a human when ANY ONE of these conditions are true:

  • The direction is the other way around than the last one
  • More than 50 milliseconds passed since the last scroll event (picked 100ms to be sure)
  • The delta value is at least as high as the previous one

Since Mac spams scroll events with descreasing delta's to the browser every 20ms when inertial scrolling is enabled, this is a pretty failsafe way. I've never had it fail on me at least. Just checking the time since the last scroll won't work because a user won't be able to scroll again if the "virtual freewheel" is still running even though they haven't scrolled for 3 seconds.

this.minScrollWheelInterval = 100; // minimum milliseconds between scrolls
this.animSpeed = 300;

this.lastScrollWheelTimestamp = 0;
this.lastScrollWheelDelta = 0;
this.animating = false;

document.addEventListener('wheel',
    (e) => {

        const now = Date.now();

        const rapidSuccession = now - this.lastScrollWheelTimestamp < this.minScrollWheelInterval;
        const otherDirection = (this.lastScrollWheelDelta > 0) !== (e.deltaY > 0);
        const speedDecrease = Math.abs(e.deltaY) < Math.abs(this.lastScrollWheelDelta);

        const isHuman = otherDirection || !rapidSuccession || !speedDecrease;

        if (isHuman && !this.animating) {
            this.animating = true; // current animation starting: future animations blocked
            $('.something').stop().animate( // perform some animation like moving to the next/previous page
                {property: value},
                this.animSpeed,
                () => {this.animating = false} // animation finished: ready for next animation
            )
        }

        this.lastScrollWheelTimestamp = now;
        this.lastScrollWheelDelta = e.deltaY;

    },
    {passive: true}
);

There's one caveat by the way: Mac also has acceleration on the scrolling, i.e.: at first, the delta value is higher for each successive event. It seems like this does not last more than 100ms or so though. So if whatever action/animation you are firing as a result of the scroll event lasts at least 100ms and blocks all other actions/animations in the meantime, this is never a problem.

kragovip
  • 426
  • 5
  • 11
  • This is beautifully simple. One small modification I needed to catch very long inertia `const speedDecrease = Math.abs(e.deltaY) <= Math.abs(this.lastScrollWheelDelta);`. – homerjam Jul 28 '21 at 10:31
  • In my experience the inertial scroll lasted much longer than 100ms (usually more than 1s, sometimes more than 2s with a really big (unrealistic) scroll). However, very helpful. – Henry Woody Nov 06 '22 at 02:27
4

Yes and no.

You can use touchdown/up, and scroll as events to look for the page moving about but those won't trigger if the OS is doing an inertial scroll. Fun, right?

One thing that you can continually detect, however, is window.pageYOffset. That value will keep changing while an inertial scroll is happening but won't throw an event. So you can come up with a set of timers to keep checking for an inertial scroll and keep running itself until the page has stopped moving.

Tricky stuff, but it should work.

OldDrunkenSailor
  • 338
  • 1
  • 5
  • 20
  • Do you know, by any chance, if there is any kind of Javascript event or timer that can be made to fire during the inertial scroll? I opened [my own question](http://stackoverflow.com/questions/15304568) about it, but so far no good answers. – Tobia Mar 18 '13 at 14:20
  • I answered in your other thread (would have done it for free but your bounty hastened my research into the topic). Basically the tl;dr is that Javascript as well as DOM changes are locked during iOS intertial scrolling. Checking out the iScroll library would probably be a good idea and would likely solve a lot of your issues. – OldDrunkenSailor Mar 18 '13 at 18:44
  • I think you may be confusing this with iOS. This question is about trackpad scrolling on a Mac which doesn't emit touch events and does emit scroll events during the inertial scroll. – Tamlyn Mar 06 '15 at 15:14
3

Oh how is this issue killing me :/

I'm in the process of creating "endless" scrolling large file viewer.

To make situation worse, this editor is embedded in page that has its own scroll bar, because its bigger than one screen.

U use overflow-x scroll for horizontal scroll, but for vertical scroll i need current line highlighter (as seen in most modern IDEs) so i'm using jquery mousewheel plugin, and scrolling moving content for line height up or down.

It works perfectly on ubuntu/chrome but on MacOS Lion/Chrome sometimes, and just sometimes, when you scroll, it doesn't prevent default scroll on the editor element, and event propagates "up" and page it self starts to scroll.

I cant even describe how much annoying that is.

As for inertial scroll it self, i successfully reduced it with two timers

var self = this;
// mouse wheel events
    $('#editorWrapper').mousewheel(function (event, delta, deltax, deltay) {

        self._thisScroll = new Date().getTime();


        //
        //this is entirely because of Inertial scrolling feature on mac os :(
        //
        if((self._thisScroll - self._lastScroll) > 5){
            //
            //
            // NOW i do actual moving of content
            //
            //

            self._lastScroll = new Date().getTime(); 
        }

5ms is value i found to have most natural feel on my MacBook Pro, and you have to scroll mouse wheel really fast to catch one of those..

Even still, sometimes on Mac listener on mac wrapper doesn't prevent default, and page scrolls down.

kodisha
  • 1,074
  • 13
  • 24
2

I'm not sure about other browsers, but the following event fires during inertial scroll on my Chrome, FF, Safari (mac):

var mousewheelevt=(/Firefox/i.test(navigator.userAgent))? "DOMMouseScroll" : "mousewheel";
function scrollEE (e) {
    console.log(e);
}
window.addEventListener(mousewheelevt, scrollEE, false);
Yuriy Polezhayev
  • 812
  • 8
  • 12
2

Well, (I might be wrong), I think that the "inertia" settings on the Mac are all computed by the system itself, the browser, or any program for that matter would just think that the user is scrolling quickly, rather than slowing down.

Rich
  • 5,603
  • 9
  • 39
  • 61
1

I had a big problem with an object animating based on scroll position after the scroll had completed, and the inertial scroll was really messing me around. I ended up calculating the velocity to determine how long the inertial scroll would last and used that to wait before animating.

var currentY = 0;
var previousY = 0;
var lastKnownVelocity = 0;
var velocityRating = 1;
function calculateVelocity() {
    previousY = currentY;
    currentY = $('#content').scrollTop();
    lastKnownVelocity = previousY - currentY;
    if (lastKnownVelocity > 20 || lastKnownVelocity < -20) {
        velocityRating = 5;
    } else {
        velocityRating = 1;
    }
}

$('#content').scroll(function () {
    // get velocity while scrolling...
    calculateVelocity();
    // wait until finished scrolling...
    clearTimeout($.data(this, 'scrollTimer'));
    $.data(this, 'scrollTimer', setTimeout(function() {
        // do your animation
        $('#content').animate({scrollTop: snapPoint}, 300);
    }, 300*velocityRating)); // short or long duration...
});
Oliver
  • 41
  • 6
1

There's a library that solves this problem.

https://github.com/d4nyll/lethargy

After installing it, use it like this:

var lethargy = new Lethargy();

$(window).bind('mousewheel DOMMouseScroll wheel MozMousePixelScroll', function(e){
  if(lethargy.check(e) === false) {
    console.log('stopping zoom event from inertia')
    e.preventDefault()
    e.stopPropagation();
  }
  console.log('Continue with zoom event from intent')
});
Christiaan Westerbeek
  • 10,619
  • 13
  • 64
  • 89
0

Following your instructions with my type of code, I had to set timeout of 270ms on each action activated by scroll to get it all smooth, so if anyone is using something similar to me here is my example if not ignore it, hope it will help you.

//Event action
    function scrollOnClick(height) {

        $('html').animate({
            scrollTop: height
        }, 'fast');
        return false;
    };
 
// Scroll on PC
  let timer = false;
  $(window).on('mousewheel', function (event) {
      if(timer != true)  {
          var heightWindow = $(window).height();
          var heightCurrent = $(window).scrollTop();
          if (event.originalEvent.wheelDelta >= 1) {
              if (heightWindow >= heightCurrent) {
                  timer= true;

                  scrollOnClick(0)
                  setTimeout(function (){
                      timer = false;

                  },270);
              }
          } else {
              if (heightCurrent < heightWindow) {
                  timer= true;

                  scrollOnClick(heightWindow)
                  setTimeout(function (){
                      timer = false;

                  },270);
              }
          }
      }
    });
Otrov
  • 19
  • 1