111

Is it possible to tell whether a scroll event was done by the browser or by the user? Specifically, when using the back button a browser may jump to the last known scroll position. If I bind to scroll event how can I tell whether this was caused by user or browser?

$(document).scroll( function(){ 
    //who did this?!
});

I see three types of situations that cause scrolling in a browser.

  1. The user performs some action. For example, uses mousewheel, arrow keys, page up/down keys, home/end keys, clicks the scrollbar or drags its thumb.
  2. The browser scrolls automatically. For example, when using the back button in your browser it will jump to the last known scroll position automatically.
  3. Javascript scrolls. For example, element.scrollTo(x,y).
Multihunter
  • 5,520
  • 2
  • 25
  • 38
mrtsherman
  • 39,342
  • 23
  • 87
  • 111
  • 1
    I'm not sure from your question, if you consider the jump using the back botton to me a scroll event of the browser or the user. Generally: What do you consider "scrolling by the browser"? If you mean scrolling initiated by your script, then all you need to do, is when your script scrolls, to either deactivate the event handler or set a flag so that the event handler knows to ignore it. – RoToRa Aug 12 '11 at 10:14
  • I considered scrolling via back button to be a "browser scroll." Anything else - mousewheel, up/down arrows, center button click, etc would be a user scroll. I guess my real question may be - is there any way to differentiate where an event came from? I did look at the properties on the event object, but couldn't find anything. The three scenarios I can imagine are browser initiated scrolling, javascript initiated scrolling and user initiated scrolling. Hope that makes things clearer. – mrtsherman Aug 12 '11 at 12:40
  • @mrtsherman I found some of these while achieving the same output: http://stackoverflow.com/questions/2834667/how-can-i-differentiate-a-manual-scroll-via-mousewheel-scrollbar-from-a-javasc – talha2k Feb 20 '13 at 11:16

11 Answers11

36

Unfortunately, there is no direct way of telling that.

I would say if you can redesign your app so that it doesn't depend on this type of flow, go for that.

If not, a workaround I can think of is to keep track of user initiated scrolls and check that to see if the scroll was triggered by the browser or by the user.

Here's an example that I put together which does this pretty well (except for browsers where jQuery history has problems with).

You need to run this locally to be able to test it fully (jsFiddle/jsbin are not good fits as they iFrame the contents).

Here's the test cases that I validated:

  • Page loads - userScroll is false
  • Scroll using mouse/keyboard - userScroll becomes true
  • Click on the link to jump to page bottom - userScroll becomes false
  • Click Back/Forward - userScroll becomes false;

<!DOCTYPE html> 
<html lang="en"> 
<head> 
    <meta charset="utf-8" /> 
    <script src="http://code.jquery.com/jquery-1.6.1.min.js"></script> 
    <script type="text/javascript" src="https://raw.github.com/tkyk/jquery-history-plugin/master/jquery.history.js"></script> 
</head> 
<body> 
    <span> hello there </span><br/> 
    <a href="#bottom"> click here to go down </a> 
    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/> 
    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/> 
    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/> 
    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/> 
    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/> 
    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/> 
    <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/> 
    <a name="bottom"> just sitting </a> 
</body> 
<script type="text/javascript"> 

var userScroll = false;     

function mouseEvent(e) { 
    userScroll = true; 
} 


$(function() { 

    // reset flag on back/forward 
    $.history.init(function(hash){ 
        userScroll = false; 
    }); 

    $(document).keydown(function(e) { 
        if(e.which == 33        // page up 
           || e.which == 34     // page dn 
           || e.which == 32     // spacebar
           || e.which == 38     // up 
           || e.which == 40     // down 
           || (e.ctrlKey && e.which == 36)     // ctrl + home 
           || (e.ctrlKey && e.which == 35)     // ctrl + end 
          ) { 
            userScroll = true; 
        } 
    }); 

    // detect user scroll through mouse
    // Mozilla/Webkit 
    if(window.addEventListener) {
        document.addEventListener('DOMMouseScroll', mouseEvent, false); 
    }

    //for IE/OPERA etc 
    document.onmousewheel = mouseEvent; 


    // to reset flag when named anchors are clicked
    $('a[href*=#]').click(function() { 
        userScroll = false;
    }); 

      // detect browser/user scroll
    $(document).scroll( function(){  
        console.log('Scroll initiated by ' + (userScroll == true ? "user" : "browser"));
    });
}); 
</script> 
</html>

Notes:

  • This doesn't track scrolling when the user drags the scrollbar with mouse. This can be added with some more code, which I left as an exercise for you.
  • event.keyCodes may vary by OS, so you may have to change that appropriately.

Hope this helps!

Mrchief
  • 75,126
  • 20
  • 142
  • 189
  • Thank you Mrchief. I think this is the best answer even though it is not what I was hoping it would be (What? there's no `event.thrower` property?!). – mrtsherman Aug 28 '11 at 04:01
  • You don't have to press control when using the home and end keys to scroll. – Curtis May 11 '19 at 01:15
  • DOMMouseScroll doesn't work on the latest Chrome on Mac. Best to use document.addEventListener('mousewheel' instead of setting onmousewheel – Curtis May 11 '19 at 01:17
14

Rather than trying to catch all the user events, it's much easier to do the opposite and handle only the programmatic events - and ignore those.

For example, this kind of code would work:

// Element that needs to be scrolled
var myElement = document.getElementById('my-container');

// Flag to tell if the change was programmatic or by the user
var ignoreNextScrollEvent = false;

function setScrollTop(scrollTop) {
    ignoreNextScrollEvent = true;
    myElement.scrollTop = scrollTop
}

myElement.addEventListener('scroll', function() {
    if (ignoreNextScrollEvent) {
        // Ignore this event because it was done programmatically
        ignoreNextScrollEvent = false;
        return;
    }

    // Process user-initiated event here
});

Then when you call setScrollTop(), the scroll event will be ignored, while if the user scroll with the mouse, keyboard or any other way, the event will be processed.

laurent
  • 88,262
  • 77
  • 290
  • 428
8

As far as I know it is impossible (without any work) to tell whenever scroll event has been issued by "user" or by other means.

You could try (as others mentioned) catch mousewheel events, then probably trying to catch keydown event on any keys that can trigger scroll (arrows, space etc.) while checking what element is currently focused, since you for example can't scroll using arrow keys while typing in an input field. In general that would be complex and messy script.

Depending on situation you're dealing with you could I guess "revert the logic", and instead of detecting user issued scroll events just hook in into any scrolls made programatically and treat any scroll events not made by your code as made by an user. Like I said it depends on a situation, and what you're trying to achive.

WTK
  • 16,583
  • 6
  • 35
  • 45
  • Thanks WTK. This was the best answer for awhile, but unfortunately for you Mrchief expounded on your answer with some code examples so I gave the bounty to him. If I could have split it I would have! – mrtsherman Aug 28 '11 at 04:02
  • 4
    That's ok. It's not about bounty but about spreading and gaining knowledge :) – WTK Aug 29 '11 at 08:20
4

Yes, it is 100% possible. I'm current using this in an application where IE is not a requirement - client facing only. When my Backbone app initiates an animation where scroll is changed - scroll occurs but does not trigger these event captures. This is tested in FF, Safari & Chrome latest.

$('html, body').bind('scroll mousedown wheel DOMMouseScroll mousewheel keyup', function(evt) {
  // detect only user initiated, not by an .animate though
    if (evt.type === 'DOMMouseScroll' || evt.type === 'keyup' || evt.type === 'mousewheel') {
    // up
    if (evt.originalEvent.detail < 0 || (evt.originalEvent.wheelDelta && evt.originalEvent.wheelDelta > 0)) { 
    // down.
    } else if (evt.originalEvent.detail > 0 || (evt.originalEvent.wheelDelta && evt.originalEvent.wheelDelta < 0)) { 
  }
}
}); 
  • 1
    Thanks for the suggestion. However, this is a binary solution - detects javascript initiated scroll vs. non-js scroll. I really need a ternary solution. (javascript, user, browser). – mrtsherman Dec 15 '14 at 15:50
3

Try using the Mousewheel and DOMMouseScroll events instead. See http://www.quirksmode.org/dom/events/scroll.html

Gerben
  • 16,747
  • 6
  • 37
  • 56
  • Thank you for this suggestion. However I don't think this has what I want. I actually need to differentiate between browser and user scroll. Not just target the mousewheel. – mrtsherman Aug 12 '11 at 12:44
  • I think the mousewheel event is fired before the onscroll event. So you can set var userscroll=true on mousewheel, and detect it in onscroll (and reset it to false). – Gerben Aug 12 '11 at 18:24
  • 3
    There are a large number of user initiated scroll events that would not be covered though. Arrow keys, window resize, etc. It is much safer to say the sender of this event is "X" than to say everything outside this event must be "X." It also does not work for me regardless since I want to identify a scroll initiated by the browser, not by the user. So to make this use case I would have to track all mousewheel scrolls and then try to deduce whether the subsequent scroll event was user based on that. I am afraid it is just unworkable. – mrtsherman Aug 12 '11 at 18:50
  • The wheel event doesn't necessarily trigger a scroll event, depending on the context. https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event – Joyce Lee Jul 23 '21 at 00:08
2

You can check the scroll position on ready. When you fire the on scroll event check to make sure the scroll position is different than it was when the page loaded. Lastly be sure to clear out the stored value once the page is scrolled.

$(function () {
    var loadScrollTop = ($(document).scrollTop() > 0 ? $(document).scrollTop() : null);
    $(document).scroll(function (e) {
        if ( $(document).scrollTop() !== loadScrollTop) {
            // scroll code here!
        }
        loadScrollTop = null;
    });
});
Rusty Jeans
  • 1,426
  • 9
  • 10
  • Thank you for this idea. There are some scenarios that this doesn't quite cover for me. Although it is a good idea and I will keep it in my back pocket if I ever need it. – mrtsherman Aug 28 '11 at 03:59
  • Simple and in most cases effective. – Aaron Jul 13 '20 at 13:15
1

Regarding to:

Specifically, when using the back button a browser may jump to the last known scroll position.

That fires very soon, after the page is rendered. You can just delay listenting to the scroll event by 1 second or so.

  • 2
    This would not be a robust solution. The time to jump is dependent on page load time. The browser delays jumping until the page is completely loaded. This prevents page elements that load and change page height (like images) from scrolling out of view. – mrtsherman Dec 15 '14 at 15:45
1

There is one more way to separate the user-created scroll: you can use the alternative event handlers, for example 'mousewheel', 'touchmove', 'keydown' with codes 38 and 40 for arrow scrolling, for scrolling with scroll bar - if 'scroll' event is fired simultaneously with 'mousedown' until 'mouseup' event.

  • There are more cases may cause scroll, for example, you try to insert some content in the scroll element before current screen. – tsh Oct 09 '21 at 09:22
-1

if you're using JQuery than there's a better answer, apparently - i'm just trying it out myself.

see: Detect jquery event trigger by user or call by code

Community
  • 1
  • 1
nathan g
  • 858
  • 9
  • 17
  • Thanks for the suggestion. However, this is a binary solution - detects javascript initiated scroll vs. non-js scroll. I really need a ternary solution. (javascript, user, browser). Sidenote, jQuery is not a requirement for your propsed solution. – mrtsherman Dec 15 '14 at 15:50
-1

It might not help with your application, but I needed to fire an event on user scroll but not programatic scroll and posting incase it helps anyone else.

Listen to the wheel event instead of scroll,

It is triggered whenever a user uses the mouse wheel or tracker pad( which I feel is how most people scroll anyway) and isn't fired when you programatically scroll. I used it to differentiate between a user scroll action and scrolling functions.

https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event


element.addEventListener('wheel', (event) => {
       //do user scroll stuff here
})

One caveat is that wheel doesn't fire on scroll on mobile, so I checked whether the device was mobile and used similar functions

if(this.mobile){
  element.addEventListener('scroll', (event) => {
       //do mobile scroll stuff here
  })
}

TimothyBuktu
  • 2,016
  • 5
  • 21
  • 35
-1

Found this very useful. Here's a coffeescript version for those so inclined.

$ ->
  S.userScroll = false

  # reset flag on back/forward
  if $.history?
    $.history.init (hash) ->
      S.userScroll = false

  mouseEvent = (e)->
    S.userScroll = true

  $(document).keydown (e) ->
    importantKey =  (e.which == 33 or        # page up
      e.which == 34 or    # page dn
      e.which == 32 or    # spacebar
      e.which == 38 or    # up
      e.which == 40 or    # down
      (e.ctrlKey and e.which == 36) or    # ctrl + home
      (e.ctrlKey and e.which == 35)    # ctrl + end
    )
    if importantKey
      S.userScroll = true;

  # Detect user scroll through mouse
  # Mozilla/Webkit
  if window.addEventListener
      document.addEventListener('DOMMouseScroll', mouseEvent, false);

  # for IE/OPERA etc
  document.onmousewheel = mouseEvent;
nottombrown
  • 515
  • 1
  • 6
  • 13