How to detect/disable inertial scrolling in Mac Safari?
Asked Answered
D

8

23

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.

Draghound answered 2/12, 2010 at 19:51 Comment(1)
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.Scandic
H
6

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.

Hallow answered 22/3, 2018 at 19:43 Comment(3)
This is beautifully simple. One small modification I needed to catch very long inertia const speedDecrease = Math.abs(e.deltaY) <= Math.abs(this.lastScrollWheelDelta);.Julian
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.Lineolate
Beautiful, I was trying to figure it out becuase I have a proftolio website that uses the fullpage.js plug-in, which works great except on mac's. when you are on a mac and you scroll down, inertia scrolling keeps scrolling down pages until it reaches the end, effectively skipping all the pages of my website, while on windows and android it just scrolls down 1 page. (Correct behaviour). I had to disable the plug-in and just program the scroll manually, but I couldn't figure out how to differentiate between inertia scrolling and the user just scrolling many times in a row. Thank youInstitutionalize
C
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.

Cesura answered 8/12, 2011 at 20:11 Comment(3)
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 about it, but so far no good answers.Gabby
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.Cesura
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.Zygo
A
4

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')
});
Aspersorium answered 31/10, 2017 at 20:30 Comment(0)
M
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.

Myrick answered 16/3, 2012 at 13:48 Comment(0)
A
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.

Affection answered 10/12, 2010 at 4:27 Comment(0)
L
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);
Leucippus answered 17/4, 2015 at 13:9 Comment(0)
H
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...
});
Holstein answered 16/5, 2014 at 16:6 Comment(0)
S
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);
              }
          }
      }
    });
Sarco answered 10/5, 2022 at 8:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.