[jquery] How to detect scroll direction

I want to run a function when someone scrolls down on an element. Something like this:

 $('div').scrollDown(function(){ alert('down') });
 $('div').scrollUp(function(){ alert('up') });

But those functions don't exist. Is there a solution to this problem? Awwwards seem to be able to do it (logo in navbar changes depending on scroll direction). Unfortunately, the source code is compressed, so no luck there.

This question is related to jquery scroll direction

The answer is


$(function(){
    var _top = $(window).scrollTop();
    var _direction;
    $(window).scroll(function(){
        var _cur_top = $(window).scrollTop();
        if(_top < _cur_top)
        {
            _direction = 'down';
        }
        else
        {
            _direction = 'up';
        }
        _top = _cur_top;
        console.log(_direction);
    });
});

Demo: http://jsfiddle.net/AlienWebguy/Bka6F/


var mousewheelevt = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel" //FF doesn't recognize mousewheel as of FF3.x
$(document).bind(mousewheelevt, 
function(e)
    {
        var evt = window.event || e //equalize event object     
        evt = evt.originalEvent ? evt.originalEvent : evt; //convert to originalEvent if possible               
        var delta = evt.detail ? evt.detail*(-40) : evt.wheelDelta //check for detail first, because it is used by Opera and FF
        if(delta > 0) 
            {
            scrollup();
            }
        else
            {
            scrolldown();
            }   
    }
);

You can use this simple plugin to add scrollUp and scrollDown to your jQuery

https://github.com/phpust/JQueryScrollDetector

var lastScrollTop = 0;
var action = "stopped";
var timeout = 100;
// Scroll end detector:
$.fn.scrollEnd = function(callback, timeout) {    
      $(this).scroll(function(){
        // get current scroll top 
        var st = $(this).scrollTop();
        var $this = $(this);
        // fix for page loads
        if (lastScrollTop !=0 )
        {
            // if it's scroll up
            if (st < lastScrollTop){
                action = "scrollUp";
            } 
            // else if it's scroll down
            else if (st > lastScrollTop){
                action = "scrollDown";
            }
        }
        // set the current scroll as last scroll top
        lastScrollTop = st;
        // check if scrollTimeout is set then clear it
        if ($this.data('scrollTimeout')) {
          clearTimeout($this.data('scrollTimeout'));
        }
        // wait until timeout done to overwrite scrolls output
        $this.data('scrollTimeout', setTimeout(callback,timeout));
    });
};

$(window).scrollEnd(function(){
    if(action!="stopped"){
        //call the event listener attached to obj.
        $(document).trigger(action); 
    }
}, timeout);

Following example will listen to MOUSE scroll only, no touch nor trackpad scrolls.

It uses jQuery.on() (As of jQuery 1.7, the .on() method is the preferred method for attaching event handlers to a document).

$('#elem').on( 'DOMMouseScroll mousewheel', function ( event ) {
  if( event.originalEvent.detail > 0 || event.originalEvent.wheelDelta < 0 ) { //alternative options for wheelData: wheelDeltaX & wheelDeltaY
    //scroll down
    console.log('Down');
  } else {
    //scroll up
    console.log('Up');
  }
  //prevent page fom scrolling
  return false;
});

Works on all browsers.

fiddle: http://jsfiddle.net/honk1/gWnNv/7/


Existing Solution

There could be 3 solution from this posting and other stackoverflow article.

Solution 1

    var lastScrollTop = 0;
    $(window).on('scroll', function() {
        st = $(this).scrollTop();
        if(st < lastScrollTop) {
            console.log('up 1');
        }
        else {
            console.log('down 1');
        }
        lastScrollTop = st;
    });

Solution 2

    $('body').on('DOMMouseScroll', function(e){
        if(e.originalEvent.detail < 0) {
            console.log('up 2');
        }
        else {
            console.log('down 2');
        }
    });

Solution 3

    $('body').on('mousewheel', function(e){
        if(e.originalEvent.wheelDelta > 0) {
            console.log('up 3');
        }
        else {
            console.log('down 3');
        }
    });

Multi Browser Test

I couldn't tested it on Safari

chrome 42 (Win 7)

  • Solution 1
    • Up : 1 event per 1 scroll
    • Down : 1 event per 1 scroll
  • Soltion 2
    • Up : Not working
    • Down : Not working
  • Solution 3
    • Up : 1 event per 1 scroll
    • Down : 1 event per 1 scroll

Firefox 37 (Win 7)

  • Solution 1
    • Up : 20 events per 1 scroll
    • Down : 20 events per 1 scroll
  • Soltion 2
    • Up : Not working
    • Down : 1 event per 1 scroll
  • Solution 3
    • Up : Not working
    • Down : Not working

IE 11 (Win 8)

  • Solution 1
    • Up : 10 events per 1 scroll (side effect : down scroll occured at last)
    • Down : 10 events per 1 scroll
  • Soltion 2
    • Up : Not working
    • Down : Not working
  • Solution 3
    • Up : Not working
    • Down : 1 event per 1 scroll

IE 10 (Win 7)

  • Solution 1
    • Up : 1 event per 1 scroll
    • Down : 1 event per 1 scroll
  • Soltion 2
    • Up : Not working
    • Down : Not working
  • Solution 3
    • Up : 1 event per 1 scroll
    • Down : 1 event per 1 scroll

IE 9 (Win 7)

  • Solution 1
    • Up : 1 event per 1 scroll
    • Down : 1 event per 1 scroll
  • Soltion 2
    • Up : Not working
    • Down : Not working
  • Solution 3
    • Up : 1 event per 1 scroll
    • Down : 1 event per 1 scroll

IE 8 (Win 7)

  • Solution 1
    • Up : 2 events per 1 scroll (side effect : down scroll occured at last)
    • Down : 2~4 events per 1 scroll
  • Soltion 2
    • Up : Not working
    • Down : Not working
  • Solution 3
    • Up : 1 event per 1 scroll
    • Down : 1 event per 1 scroll

Combined Solution

I checked that side effect from IE 11 and IE 8 is come from if else statement. So, I replaced it with if else if statement as following.

From the multi browser test, I decided to use Solution 3 for common browsers and Solution 1 for firefox and IE 11.

I referred this answer to detect IE 11.

    // Detect IE version
    var iev=0;
    var ieold = (/MSIE (\d+\.\d+);/.test(navigator.userAgent));
    var trident = !!navigator.userAgent.match(/Trident\/7.0/);
    var rv=navigator.userAgent.indexOf("rv:11.0");

    if (ieold) iev=new Number(RegExp.$1);
    if (navigator.appVersion.indexOf("MSIE 10") != -1) iev=10;
    if (trident&&rv!=-1) iev=11;

    // Firefox or IE 11
    if(typeof InstallTrigger !== 'undefined' || iev == 11) {
        var lastScrollTop = 0;
        $(window).on('scroll', function() {
            st = $(this).scrollTop();
            if(st < lastScrollTop) {
                console.log('Up');
            }
            else if(st > lastScrollTop) {
                console.log('Down');
            }
            lastScrollTop = st;
        });
    }
    // Other browsers
    else {
        $('body').on('mousewheel', function(e){
            if(e.originalEvent.wheelDelta > 0) {
                console.log('Up');
            }
            else if(e.originalEvent.wheelDelta < 0) {
                console.log('Down');
            }
        });
    }

Here is a sample showing an easy way to do it. The script is:

$(function() {
  var _t = $("#container").scrollTop();
  $("#container").scroll(function() {
    var _n = $("#container").scrollTop();
    if (_n > _t) {
      $("#target").text("Down");
    } else {
      $("#target").text("Up");
    }
    _t = _n;
  });
});

The #container is your div id. The #target is just to see it working. Change to what you want when up or when down.

EDIT

The OP didn't say before, but since he's using a div with overflow: hidden, scrolling doesn't occur, then the script to detect the scroll is the least of it. Well, how to detect something that does not happen?!

So, the OP himself posted the link with what he wants, so why not use that library? http://cdn.jquerytools.org/1.2.5/full/jquery.tools.min.js.

The call is just:

$(function() {
    $(".scrollable").scrollable({ vertical: true, mousewheel: true });
});

This one deserves an update - nowadays we have the wheel event :

_x000D_
_x000D_
$(function() {_x000D_
_x000D_
$(window).on('wheel', function(e) {_x000D_
_x000D_
 var delta = e.originalEvent.deltaY;_x000D_
_x000D_
 if (delta > 0) $('body').text('down');_x000D_
 else $('body').text('up');_x000D_
_x000D_
 return false; // this line is only added so the whole page won't scroll in the demo_x000D_
});_x000D_
});
_x000D_
body {_x000D_
  font-size: 22px;_x000D_
  text-align: center;_x000D_
  color: white;_x000D_
  background: grey;_x000D_
}
_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
_x000D_
_x000D_
_x000D_

Support has been pretty good on modern browsers for quite a while already :

  • Chrome 31+
  • Firefox 17+
  • IE9+
  • Opera 18+
  • Safari 7+

https://developer.mozilla.org/en-US/docs/Web/Events/wheel

If deeper browser support is required, probably best to use mousewheel.js instead of messing about :

https://plugins.jquery.com/mousewheel/

_x000D_
_x000D_
$(function() {_x000D_
_x000D_
$(window).mousewheel(function(turn, delta) {_x000D_
_x000D_
 if (delta > 0) $('body').text('up');_x000D_
 else $('body').text('down');_x000D_
_x000D_
 return false; // this line is only added so the whole page won't scroll in the demo_x000D_
});_x000D_
});
_x000D_
body {_x000D_
  font-size: 22px;_x000D_
  text-align: center;_x000D_
  color: white;_x000D_
  background: grey;_x000D_
}
_x000D_
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>_x000D_
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js"></script>
_x000D_
_x000D_
_x000D_