Scroll event firing too many times. I only want it to fire a maximum of, say, once per second

Javascript

Javascript Problem Overview


I have a page with "infinite scroll". It calculates the difference between the end of the page and the current page and loads more content if this difference is small enough. The code is soemthing like this using jQuery:

$(window).on('scroll', function() {
    if (window.pageYOffset > loadMoreButton.offsetTop - 1000)
        # load more content via ajax
}

Now, the problem is that every time I scroll, this event fires multiple times per scroll. I would like fire at most every x milliseconds. How would I do this?

Javascript Solutions


Solution 1 - Javascript

Check out the Underscore.js library's "throttle" method.

http://underscorejs.org/#throttle

The example it gives is exactly what you're asking about - limiting how often you have to handle scroll events.

Solution 2 - Javascript

One way to solve this problem is to define a time interval and only process a scroll event once within that time interval. If more than one scroll event comes in during that time interval, you ignore it and process it only when that time interval has passed.

var scrollTimer, lastScrollFireTime = 0;

$(window).on('scroll', function() {

    var minScrollTime = 100;
    var now = new Date().getTime();
    
    function processScroll() {
        console.log(new Date().getTime().toString());
    }

    if (!scrollTimer) {
        if (now - lastScrollFireTime > (3 * minScrollTime)) {
            processScroll();   // fire immediately on first scroll
            lastScrollFireTime = now;
        }
        scrollTimer = setTimeout(function() {
            scrollTimer = null;
            lastScrollFireTime = new Date().getTime();
            processScroll();
        }, minScrollTime);
    }
});

This will fire the first scroll event immediately and then get you a scroll event approximately once every 100ms while the scrollbar is being moved and then one final event after the scrollbar stops moving. You can adjust the frequency of the event by changing the argument to the setTimeout (what is currently set to 100).

There is a demo here: http://jsfiddle.net/jfriend00/EBEqZ/ which you need to open a debug console window, start moving the scrollbar in the content window and then watch the time of each scroll event in the debug console window. On my version of Chrome, they are set for a minimum spacing of 100ms and they seem to occur every 100-200ms.

Solution 3 - Javascript

There is a cool explanation from John Resig, the creator of jQuery to resolve this situation.

var outerPane = $details.find(".details-pane-outer"),
    didScroll = false;
 
$(window).scroll(function() {
    didScroll = true;
});
 
setInterval(function() {
    if ( didScroll ) {
        didScroll = false;
        // Check your page position and then
        // Load in more results
    }
}, 250);

The source: http://ejohn.org/blog/learning-from-twitter/

Solution 4 - Javascript

var isWorking = 0;

$(window).on('scroll', function()
{
    if(isWorking==0)  
    {
         isWorking=1;
		 if (window.pageYOffset > loadMoreButton.offsetTop - 1000)
   		 # load more content via ajax
	     setTimeout(function(){isWorking=0},1000);
    }
}

Solution 5 - Javascript

var now = new Date().getTime();
$(window).scroll( function () {
    if (window.pageYOffset > loadMoreButton.offsetTop - 1000)
    {
        if (new Date().getTime() - now > 1000)
        {
            console.log("Task executed once per second");
            now = new Date().getTime();
        }
    }
});

Or

You can use Throttling fonction calls: https://remysharp.com/2010/07/21/throttling-function-calls">throttling-function-calls</a>

function throttle(fn, threshhold, scope) {
  threshhold || (threshhold = 250);
  var last,
      deferTimer;
  return function () {
    var context = scope || this;

    var now = +new Date,
        args = arguments;
    if (last && now < last + threshhold) {
      // hold on to it
      clearTimeout(deferTimer);
      deferTimer = setTimeout(function () {
        last = now;
        fn.apply(context, args);
      }, threshhold);
    } else {
      last = now;
      fn.apply(context, args);
    }
  };
}

You can call it like this:

$('body').on('mousemove', throttle(function (event) {
  console.log('tick');
}, 1000));

Solution 6 - Javascript

Here is a solution that doesn't require the use of an extra JS library or plugin, that aims for simplicity. It might not be as efficient as other implementations but it is definitely a step up from firing your main event every time you scroll.

This was taken from this blog post by Danny Van Kooten. Which I have used in delaying my onscroll() events for my back-to-top button on my blog.

var timer;
$(window).scroll(function() {
    if(timer) {
        window.clearTimeout(timer);
    }
    timer = window.setTimeout(function() {
       // actual code here. Your call back function.
    console.log( "Firing!" );
    }, 100);
});

You can also further improve performance by moving out variables out of the callback function to avoid unnecessary recalculations, for example the value of $(window).height() or height of some static div element that won't change once the page is loaded.

Here's an example that is adapted from my use case.

var scrollHeight = $("#main-element").height(); //never changes, no need to recalculate.
$(window).on('scroll', function() {
	if (timer) 
		window.clearTimeout(timer);
	timer = window.setTimeout(function() {
		var scrollPosition = $(window).height() + $(window).scrollTop();	
	    if ($(window).scrollTop() < 500)
	    	$(".toggle").fadeIn(800);
	    else 
            $(".toggle").fadeOut(800);
    }, 150); //only fire every 150 ms.
});

This limits the actual function to only execute every 150ms, or else reset the timer back to 0 if 150ms has not passed. Tweak the value to suit what you need.

Solution 7 - Javascript

the scroll fire multiple times is correct and you should able to get the scroll position differently each time. I think you need to set a timer when you first get in the scroll event like you mentioned x milliseconds, and also record the time stamp, and then next time scroll event fire, check the last trigger time and ignore it if it's within x milliseconds, and do the real job in your Timer action.

Solution 8 - Javascript

One does not need a ton of local variables for a decent throttle function. The purpose of a throttle function is to reduce browser resources, not to apply so much overhead that you are using even more. As proof of evidence of this claim, I have devised a throttle function that has only 5 'hanging' variables referenes in its scope. Additionally, my different uses for throttle functions require many different circumstances for them. Here is my list of things that I believe 'good' throttle function needs.

  • Immediately calls the function if it has been more than interval MS since the last call.
  • Avoids executing function for another interval MS.
  • Delays excessive event firing instead of dropping the event altogether.
  • Updates the delayed event object on successive calls so that it doesn't become 'stale'.

And, I believe that the following throttle function satisfies all of those.

function throttle(func, alternateFunc, minimumInterval) {
    var executeImmediately = true, freshEvt = null;
    return function(Evt) {
        if (executeImmediately) { // Execute immediatly
            executeImmediately = false;
            setTimeout(function(f){ // handle further calls
                executeImmediately = true;
                if (freshEvt !== null) func( freshEvt );
                freshEvt = null;
            }, minimumInterval);
            return func( Evt );
        } else { // Delayed execute
            freshEvt = Evt;
            if (typeof alternateFunc === "function") alternateFunc( Evt );
        }
    };
}

Then, to wrap this throttle function around DOM event listeners:

var ltCache = [];
function listen(obj, evt, func, _opts){
    var i = 0, Len = ltCache.length, lF = null, options = _opts || {};
    a: {
        for (; i < Len; i += 4)
            if (ltCache[i] === func &&
              ltCache[i+1] === (options.alternate||null) &&
              ltCache[i+2] === (options.interval||200)
            ) break a;
        lF = throttle(func, options.alternate||null, options.interval||200);
        ltCache.push(func, options.alternate||null, options.interval||200, lF);
    }
    obj.addEventListener(evt, lF || ltCache[i+3], _opts);
};
function mute(obj, evt, func, options){
    for (var i = 0, Len = ltCache.length; i < Len; i += 4)
        if (ltCache[i] === func &&
          ltCache[i+1] === (options.alternate||null) &&
          ltCache[i+2] === (options.interval||200)
        ) return obj.removeEventListener(evt, ltCache[i+3], options);
}

Example usage:

function throttle(func, alternateFunc, minimumInterval) {
    var executeImmediately = true, freshEvt = null;
    function handleFurtherCalls(f){
        executeImmediately = true;
        if (freshEvt !== null) func( freshEvt );
        freshEvt = null;
    };
    return function(Evt) {
        if (executeImmediately) { // Execute immediatly
            executeImmediately = false;
            setTimeout(handleFurtherCalls, minimumInterval);
            return func( Evt );
        } else { // Delayed execute
            freshEvt = Evt;
            if (typeof alternateFunc === "function") alternateFunc( Evt );
        }
    };
}
var ltCache = [];
function listen(obj, evt, func, _opts){
    var i = 0, Len = ltCache.length, lF = null, options = _opts || {};
    a: {
        for (; i < Len; i += 4)
            if (ltCache[i] === func &&
              ltCache[i+1] === (options.alternate||null) &&
              ltCache[i+2] === (options.interval||200)
            ) break a;
        lF = throttle(func, options.alternate||null, options.interval||200);
        ltCache.push(func, options.alternate||null, options.interval||200, lF);
    }
    obj.addEventListener(evt, lF || ltCache[i+3], _opts);
};
function mute(obj, evt, func, options){
    for (var i = 0, Len = ltCache.length; i < Len; i += 4)
        if (ltCache[i] === func &&
          ltCache[i+1] === (options.alternate||null) &&
          ltCache[i+2] === (options.interval||200)
        ) return obj.removeEventListener(evt, ltCache[i+3], options);
}
var numScrolls = 0, counter = document.getElementById("count");
listen(window, 'scroll', function whenbodyscrolls(){
    var scroll = -document.documentElement.getBoundingClientRect().top;
    counter.textContent = numScrolls++;
    if (scroll > 900) {
      console.log('Body scrolling stoped!');
      mute(window, 'scroll', whenbodyscrolls, true);
    }
}, true);

<center><h3>\/ Scroll Down The Page \/</h3></center>
<div style="position:fixed;top:42px"># Throttled Scrolls: <span id="count">0</span></div>
<div style="height:192em;background:radial-gradient(circle at 6em -5em, transparent 0px, rgba(128,0,0,.4) 90em),radial-gradient(circle at 10em 40em, rgba(255,255,255,.8) 0px, rgba(128,0,0,.02) 50em),radial-gradient(circle at 4em 80em, rgba(0,192,0,.75) 0px,rgba(0,128,0,.56) 10em,rgba(255,0,96,.03125) 30em),radial-gradient(circle at 86em 24em, rgba(255,0,0,.125) 0px,rgba(0,0,255,.0625) 60em,transparent 80em);"></div>
<style>body{margin:0}</style>

By default, this throttles the function to at most one call every 200ms. To change the interval to a different number of milliseconds, then pass a key named "interval" in the options argument and set it to the desired milliseconds.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJonathan OngView Question on Stackoverflow
Solution 1 - JavascriptnicholaidesView Answer on Stackoverflow
Solution 2 - Javascriptjfriend00View Answer on Stackoverflow
Solution 3 - JavascriptHolloWView Answer on Stackoverflow
Solution 4 - JavascriptAlan KurasView Answer on Stackoverflow
Solution 5 - JavascriptA. MorelView Answer on Stackoverflow
Solution 6 - JavascriptmatrixanomalyView Answer on Stackoverflow
Solution 7 - JavascriptSimon WangView Answer on Stackoverflow
Solution 8 - JavascriptJack GView Answer on Stackoverflow