7

The following piece of code loads the next page, when the user scrolls to the bottom. However, sometimes it is repeating itself — when the user scrolls too rapidly, or scrolls whilst the AJAX is still being loaded.

Is there a way to prevent it from firing multiple times? So for example, nothing can be loaded while the AJAX is being called, or the AJAX can only be called once a second?

Any help would be great.

 $(window).scroll(function() {

   if( $(window).scrollTop() + $(window).height() == $(document).height()) {

    if (firstURL !== null) {

      $.get(firstURL, function(html) { // this gets called multiple times on erratic scrolling
        firstURL = '';
        var q = $(html).find('.post');
        l = $(html).filter('div.bottom-nav');
        if( l[0].childNodes.length > 0 ){
            firstURL = l[0].children[0].getAttribute('href');
        } else {
          firstURL =  null;
        }

          q.imagesLoaded( function() {
            jQuery(".content").append(q).masonry( 'appended', q, true );
           });
      });
       }
   }
});
  • throttling is what you are looking for. either don't send the request more than once per second, or don't send a request if one is already pending. – Kevin B Apr 18 '14 at 15:19

5 Answers5

13

Just add a flag :

var ready = true; //Assign the flag here

$(window).scroll(function() {
    //Check the flag here. Check it first, it's better performance wise.
    if(ready && $(window).scrollTop() + $(window).height() == $(document).height()) { 
        ready = false; //Set the flag here

        if (firstURL !== null) {

            $.get(firstURL, function(html) { // this gets called multiple times on erratic scrolling

                firstURL = '';
                var q = $(html).find('.post');
                l = $(html).filter('div.bottom-nav');
                if( l[0].childNodes.length > 0 ){
                    firstURL = l[0].children[0].getAttribute('href');
                } else {
                    firstURL =  null;
                }

                q.imagesLoaded( function() {
                    jQuery(".content").append(q).masonry( 'appended', q, true );
                });
            }).always(function(){
                ready = true; //Reset the flag here
            });
        }
    }
});
Karl-André Gagnon
  • 32,531
  • 5
  • 47
  • 70
1

I had a similar issue, that scrolling the window fired my function multiple times (manupulating my img slider's properties). To effectively deal with that matter you can defer the execution of scroll handler and use an additional 'page is being scrolled' flag to prevent multiple handler calls. Check out the example below, you can surely addopt the approach to your case.

$(function()
{
    var pageFold = 175; //scrolling threshold

    var doScroll = false; //init
    var timeoutScroll = 100; //delay

    var windowScrolled = false; //initial scrolling indicatior
    var windowScrolling = false; //current scrolling status indicator

    //load next page handler
    function loadNextPage()
    {
        if(windowScrolling != true)
        { 
           //and do ajax stuff - your code
        }
    }

   //check if page scrolled below threshold handler
   function foldedBelow()
   {
        //nice scrolled px amount detection
        return (Math.max($('body').scrollTop(), $('html').scrollTop()) > pageFold);
   }

   //actual scrolled handler
   function doWindowScroll()
   {
        windowScrolled = true;
        if(foldedBelow())
        {
                    loadNextPage();
        }
        windowScrolling = false;
   }

    //deffered scroll hook
    $(window).scroll(function(e){
        windowScrolling = true;
        clearTimeout(doScroll);
        doScroll = setTimeout(doWindowScroll, timeoutScroll);

    });   
});
Gadoma
  • 6,095
  • 1
  • 26
  • 33
0

When I did something like this I implemented a timed scroll handler that calls a custom scrolled_to_bottom-event.

(function($, window, document){
    "use strict";

    var $document = $(document);
    var $window = $(window);

    var _throttleTimer = null;
    var _throttleDelay = 100;

    function ScrollHandler(event) {
        //throttle event:
        clearTimeout(_throttleTimer);
        _throttleTimer = setTimeout(function () {
            if ($window.scrollTop() + $window.height()  > $document.height() - 400) {
                console.log('fire_scrolled_to_bottom');
                $document.trigger('scrolled_to_bottom');
            }

        }, _throttleDelay);
    }

    $document.ready(function () {
        $window
            .off('scroll', ScrollHandler)
            .on('scroll', ScrollHandler);

    });
}(jQuery, window, document));

And then in my object handling the reload I bound that event with a flag-check if it was already loading.

handler = {
        ...,
    isLoading: false,
    bind: {
        var self = this;
        $document.on('scrolled_to_bottom', function () {
            if (self.isLoading) {

                return;
            }

            self.nextPage();
        });

    }
    nextPage(): function () {
        var self = this;
        this.isLoading = true;

        $.ajax({
            url: url,
            data: self.searchData,
            dataType: "json",
            type: "POST",
            success: function (json) {
                // do what you want with respone
            },
            error: function (xhr, statusText, errorThrown) {
                bootbox.alert('An error occured.');
            },
            complete: function () {
                self.isLoading = false;
            }
        });
    },
    init: function () {
        this.doInitStuff();
        this.bind();
    }
}

This way I seperated the concerns and can reuse the triggering nicely, and easily add functionality if other things should happen on reload.

k0pernikus
  • 41,137
  • 49
  • 170
  • 286
0

Try storing some kind of data that stores whether the page is currently loading new items. Maybe like this:

$(window).data('ajaxready', true).scroll(function(e) {
    if ($(window).data('ajaxready') == false) return;

    if ($(window).scrollTop() >= ($(document).height() - $(window).height())) {
        $('div#loadmoreajaxloader').show();
        $(window).data('ajaxready', false);
        $.ajax({
            cache: false,
            url: 'loadmore.php?lastid=' + $('.postitem:last').attr('id'),
            success: function(html) {
                if (html) {
                    $('#postswrapper').append(html);
                    $('div#loadmoreajaxloader').hide();
                } else {
                    $('div#loadmoreajaxloader').html();
                }
                $(window).data('ajaxready', true);
            }
        });
    }
});

Right before the Ajax request is sent, a flag is cleared signifying that the document is not ready for more Ajax requests. Once the Ajax completes successfully, it sets the flag back to true, and more requests can be triggered.

copied : jQuery Infinite Scroll - event fires multiple times when scrolling is fast

Community
  • 1
  • 1
Abhishek Goel
  • 15,517
  • 8
  • 81
  • 62
0

Here is my solution. You can get an idea and apply it to yours. Also to help others.

  • You can execute your method first with condition: if(loadInterval === null). That means if we already waited for 5 secs.
  • Assign loadInterval = setTimeout(), then nullify the variable after 5 secs.

Here is sample code.

      //declare outside
      var loadInterval = null;
      // ..... 
      // .....
  $(window).scroll(function() {

    if ($('.loadmore').isOnScreen() === true) {

      //No waiting registered, we can run loadMore
      if(loadInterval === null) {

        // This console.log executes in 5 seconds interval
        console.log('Just called ' + new Date());
        //  your code in here is prevented from running many times on scroll

        // Register setTimeout() to wait for some seconds. 
        // The code above will not run until this is nullified
        loadInterval = setTimeout(function(){
            //Nullified interval after 5 seconds
            loadInterval = null;}
        , 5000);  
      }
    }
  });

I post here the IsOnScreen() plugin for jQuery (i found it on stackoverflow :)

$.fn.isOnScreen = function() {

  var win = $(window);

  var viewport = {
    top: win.scrollTop(),
    left: win.scrollLeft()
  };
  viewport.right = viewport.left + win.width();
  viewport.bottom = viewport.top + win.height();

  var bounds = this.offset();
  bounds.right = bounds.left + this.outerWidth();
  bounds.bottom = bounds.top + this.outerHeight();

  return (!(viewport.right < bounds.left || viewport.left > bounds.right || viewport.bottom < bounds.top || viewport.top > bounds.bottom));

};
Maxali
  • 1,752
  • 2
  • 15
  • 23