62

I'd like to do some fancy jQuery stuff when the user scrolls the page. But I have no idea how to tackle this problem, since there is only the scroll() method.

Any ideas?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
dantz
  • 1,842
  • 4
  • 21
  • 25

9 Answers9

76

You can make the scroll() have a time-out that gets overwritten each times the user scrolls. That way, when he stops after a certain amount of milliseconds your script is run, but if he scrolls in the meantime the counter will start over again and the script will wait until he is done scrolling again.

Update:

Because this question got some action again I figured I might as well update it with a jQuery extension that adds a scrollEnd event

// extension:
$.fn.scrollEnd = function(callback, timeout) {          
  $(this).on('scroll', function(){
    var $this = $(this);
    if ($this.data('scrollTimeout')) {
      clearTimeout($this.data('scrollTimeout'));
    }
    $this.data('scrollTimeout', setTimeout(callback,timeout));
  });
};

// how to call it (with a 1000ms timeout):
$(window).scrollEnd(function(){
    alert('stopped scrolling');
}, 1000);
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>

<div style="height: 200vh">
  Long div
</div>
brc-dd
  • 10,788
  • 3
  • 47
  • 67
Stephan Muller
  • 27,018
  • 16
  • 85
  • 126
64

Here is a simple example using setTimeout to fire a function when the user stops scrolling:

(function() {        
    var timer;
    $(window).bind('scroll',function () {
        clearTimeout(timer);
        timer = setTimeout( refresh , 150 );
    });
    var refresh = function () { 
        // do stuff
        console.log('Stopped Scrolling'); 
    };
})();

The timer is cleared while the scroll event is firing. Once scrolling stops, the refresh function is fired.

Or as a plugin:

$.fn.afterwards = function (event, callback, timeout) {
    var self = $(this), delay = timeout || 16;

    self.each(function () { 
        var $t = $(this);
        $t.on(event, function(){
            if ($t.data(event+'-timeout')) {
                clearTimeout($t.data(event+'-timeout'));
            }
            $t.data(event + '-timeout', setTimeout(function () { callback.apply($t); },delay));
        })
    });
    return this;
};

To fire callback after 100ms of the last scroll event on a div (with namespace):

$('div.mydiv').afterwards('scroll.mynamespace', function(e) {
        // do stuff when stops scrolling
        $(this).addClass('stopped');
    }, 100
);

I use this for scroll and resize.

Phil M
  • 975
  • 1
  • 9
  • 13
  • 3
    This is referred to as a "debounce" approach. More info (and reusable function) here: http://davidwalsh.name/function-debounce – allicarn Mar 11 '15 at 15:16
13

Here is another more generic solution based on the same ideas mentioned:

var delayedExec = function(after, fn) {
    var timer;
    return function() {
        timer && clearTimeout(timer);
        timer = setTimeout(fn, after);
    };
};

var scrollStopper = delayedExec(500, function() {
    console.log('stopped it');
});

document.getElementById('box').addEventListener('scroll', scrollStopper);
xat
  • 346
  • 3
  • 5
  • Also, you can control the quickness of this event being fired by changing the value 500 to a lower value(~~100) – DHRUV GAJWA Mar 17 '20 at 15:27
3

I had the need to implement onScrollEnd event discussed hear as well. The idea of using timer works for me.

I implement this using JavaScript Module Pattern:

var WindowCustomEventsModule = (function(){

    var _scrollEndTimeout = 30;

    var _delayedExec = function(callback){
        var timer;
        return function(){
            timer && clearTimeout(timer);
            timer = setTimeout(callback, _scrollEndTimeout);
        }
    };

    var onScrollEnd = function(callback) { 
        window.addEventListener('scroll', _delayedExec(callback), false);         
    };

    return {
        onScrollEnd: onScrollEnd
    }
})();

// usage example
WindowCustomEventsModule.onScrollEnd(function(){
    //
    // do stuff
    //
});

Hope this will help / inspire someone

2

Why so complicated? As the documentation points out, this http://jsfiddle.net/x3s7F/9/ works!

$('.frame').scroll(function() {
 $('.back').hide().fadeIn(100);
}

http://api.jquery.com/scroll/.


Note: The scroll event on Windows Chrome is differently to all others. You need to scroll fast to get the same as result as in e.g. FF. Look at https://liebdich.biz/back.min.js the "X" function.

Some findings from my how many ms a scroll event test:

  • Safari, Mac FF, Mac Chrome: ~16ms an event.
  • Windows FF: ~19ms an event.
  • Windows Chrome: up to ~130ms an event, when scrolling slow.
  • Internet Explorer: up to ~110ms an event.

http://jsfiddle.net/TRNCFRMCN/1Lygop32/4/.

loveNoHate
  • 1,549
  • 13
  • 21
  • Actually, this works quite well. Unfortunately, use of the scroll bar in the demo does not work, although I believe this is just because of the `fadeIn` function. Will have to conduct more testing to find out if there are any more bugs, but well done, works quite well! The other solutions were far to complicated for such a small task. – Fizzix Apr 09 '14 at 05:09
  • Thanks. To the uncommented downvoter: "Better now?" %)P – loveNoHate Dec 17 '14 at 07:50
  • now if I want to keep condition for only scrollup event than how can I detect scrollup?? can you please help? – Vijaysinh Parmar Jun 21 '16 at 09:59
1

There is no such event as 'scrollEnd'. I recommend that you check the value returned by scroll() every once in a while (say, 200ms) using setInterval, and record the delta between the current and the previous value. If the delta becomes zero, you can use it as your event.

dpq
  • 9,028
  • 10
  • 49
  • 69
  • 1
    Unless you keep a reference to your handler and call `clearInterval` when the delta becomes zero, or simply use `setTimeout` instead. – dpq Aug 10 '11 at 09:31
  • 1
    downvote this answer by incident, and it's [locked](https://meta.stackexchange.com/questions/75477/idea-behind-your-vote-is-now-locked-in-unless-this-answer-is-edited) now... sorry for that – blackmiaool Jan 05 '18 at 09:06
1

There are scrollstart and scrollstop functions that are part of jquery mobile.

Example using scrollstop:

$(document).on("scrollstop",function(){
   alert("Stopped scrolling!");
});

Hope this helps someone.

Dima
  • 655
  • 2
  • 8
  • 20
  • This does not seem to fire for me :( –  Jun 28 '17 at 15:33
  • @RatherNotsay It's not working for you? I have this in production, and it seems to work just fine. Did you include the mobile version of jquery? It is not the same as jquery. – Dima Jun 29 '17 at 03:28
  • I definitely had JQuery Mobile, but it may have been a custom build missing that component? I've moved on for now, but if I revisit I will update. –  Jun 29 '17 at 13:37
1

The scrollEnd event is coming. It's currently experimental and is only supported by Firefox. See the Mozilla documentation here - https://developer.mozilla.org/en-US/docs/Web/API/Document/scrollend_event

Once it's supported by more browsers, you can use it like this...

document.onscrollend = (event) => {
    console.log('Document scrollend event fired!');
};
David Jey
  • 11
  • 3
0

I pulled some code out of a quick piece I cobbled together that does this as an example (note that scroll.chain is an object containing two arrays start and end that are containers for the callback functions). Also note that I am using jQuery and underscore here.

$('body').on('scroll', scrollCall);
scrollBind('end', callbackFunction);
scrollBind('start', callbackFunction);

var scrollCall = function(e) {
    if (scroll.last === false || (Date.now() - scroll.last) <= 500) {
        scroll.last = Date.now();
        if (scroll.timeout !== false) {
            window.clearTimeout(scroll.timeout);
        } else {
            _(scroll.chain.start).each(function(f){
                f.call(window, {type: 'start'}, e.event);
            });
        }
        scroll.timeout = window.setTimeout(self.scrollCall, 550, {callback: true, event: e});
        return;
    }
    if (e.callback !== undefined) {
        _(scroll.chain.end).each(function(f){
            f.call(window, {type: 'end'}, e.event);
        });
        scroll.last = false;
        scroll.timeout = false;
    }
};

var scrollBind = function(type, func) {
    type = type.toLowerCase();
    if (_(scroll.chain).has(type)) {
        if (_(scroll.chain[type]).indexOf(func) === -1) {
            scroll.chain[type].push(func);
            return true;
        }
        return false;
    }
    return false;
}
whoughton
  • 1,395
  • 11
  • 21