9

I'm trying to figure out a clean way to aggregate mousemove events so that I ensure my code gets called, but only once every 250-300 milliseconds.

I've thought about using something like the following, but was wondering if there was a better pattern, or something jQuery provides that will do the same thing:

var mousemove_timeout = null;

$('body').mousemove(function() {
  if (mousemove_timeout == null) {
    mousemove_timeout = window.setTimeout(myFunction, 250);
  }
});

function myFunction() {
  /*
   * Run my code...
   */

  mousemove_timeout = null;
}

EDIT: The accepted answer below would work perfectly for this situation, however, I found that the mousestop() functionality provided in the answer actually eliminated my need for the aggregation, so if you're reading this question and looking for an answer, see if the mousestop plugin is what you really need!

Topher Fangio
  • 20,372
  • 15
  • 61
  • 94

9 Answers9

20

After I tried the solution in the accepted answer, I found out that if the mouse keep moving constantly, especially in circular motion, mousemove() event is fired continuously, but the mouse coordinates remain the same. So I came up with a simpler solution which eliminates mousestop() and setTimeout.

$("body").mousemove(function (e) {
        if (enableHandler) {
            handleMouseMove(e);
            enableHandler = false;
        }
});

timer = window.setInterval(function(){
    enableHandler = true;
}, 100);

This will correctly call handleMouseMove() approximately every 100ms. (Note that I said approximately because time delays and intervals in JavaScript is not real-time guaranteed)

Ethan
  • 1,488
  • 3
  • 19
  • 24
  • 1
    +1 - Thanks! This is actually the type of solution that I was looking for originally. This is a much cleaner approach than the accepted answer, but, the mousestop plugin is still what I went with. – Topher Fangio Jan 10 '12 at 13:38
  • Is there a way to stop the timer when mouse is not moving? – Mokhlesur Rahman Dec 10 '14 at 07:26
5

Your code is fine except that you should clear the timeout before setting it to null or it might leak:

window.clearTimeout(mousemove_timeout);
mousemove_timeout = null;

As an alternative you could use mousemove/mousestop in conjunction with window.setInterval

var timer = null;
var isIntervalSet = false;

$('body').mousemove(function() {
    if (isIntervalSet) {
        return;
    }
    timer = window.setInterval(function() {
        /*
        * Run my code...
        */    
    }, 250);
    isIntervalSet = true;
}).mousestop(function() {
    isIntervalSet = false;
    window.clearTimeout(timer);
    timer = null;
});
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • +1 - Thanks for the tip! Is this the best/cleanest way though? Seems like a bit of a hack... – Topher Fangio Jan 10 '11 at 15:44
  • 1
    Yes indeed it is a bit of a hack, `window.setInterval` would be more adapted to this scenario. – Darin Dimitrov Jan 10 '11 at 15:49
  • Accepted! - Turns out, the `mousestop` function is exactly what I need and will eliminate the timeout code entirely. Thanks so much! – Topher Fangio Jan 10 '11 at 16:02
  • Something's not right with this approach: if the mouse keep moving constantly, mousemove event keep getting fired, but the mouse coordinates remain the same. – Ethan Jan 05 '12 at 13:58
4

A solution and a question^^

What about this approach without a global var. Is that a suitable solution?

$(function() {
    $("#foo").mousemove((function() {
        var timer = null;

        return function() {
            if (timer !== null) {
                window.clearTimeout(timer);
            }
            timer = window.setTimeout(foo, 250);
        };
    })());
});

function foo() {
    //...
}
Andreas
  • 21,535
  • 7
  • 47
  • 56
  • 1
    +1 - I definitely like not using a global variable. Although I haven't tried this code, it does look fairly clean and intuitive. – Topher Fangio May 24 '13 at 14:11
3

A simple way of gettin the mouse position in a custom period of miliseconds

var timer;
var refresh_time = 50;
var x = 0;
jQuery('body').mousemove(function(evt) {
  if (timer)
    clearTimeout(timer);
    timer = setTimeout(function(){
      var mouse_x = evt.clientX;
      if(mouse_x != x){
        x = mouse_x;
        console.log('mouse is on a new x position' + x);    
      }
    }, refresh_time);        
})
chilljul
  • 319
  • 3
  • 6
2

You seek code Throttling / Debouncing.

http://benalman.com/projects/jquery-throttle-debounce-plugin/ http://drupalmotion.com/article/debounce-and-throttle-visual-explanation

Sample from underscore.jS

// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
    var timeout;
    return function() {
        var context = this, args = arguments;
        var later = function() {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        var callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};
skibulk
  • 3,088
  • 1
  • 34
  • 42
2

I know I'm a little late to the party, but it might be of use to people visiting this thread, here is my 2 cents.

Using the modulus operator and simple number increments, you can throttle the firing rate of your function with minimal performance hit, like so;

var fired = 0;
$('#element').on('mousemove', function(){
   fired++;
   // Fire 5x less than usual
   if(!(fired % 5) || fired == 1) yourFunction();
})

Additionally, if you're afraid to hit the max integer limit, you can reset the fired variable every X thousand hits (again, using the modulus operator) or by using the mouseout event.

Thieu
  • 168
  • 6
1

You can save a few lines by using the timeout to null the timer:

var paused = null;

$("body").mousemove(function (e) {
    if (!paused){
        /** your code here **/
        paused = setTimeout(function(){paused=null}, 250);
    }
});
Peter Hollingsworth
  • 1,870
  • 1
  • 18
  • 18
1

This was a really interesting question. I found a less hackish way to do this on, and you can check out this live demo of the following snippet:

({
    event: null,
    interval: null,
    init: function(){
        var self = this;
        $(document).bind("mousemove", function(e){self.event=e;});
        this.interval = setInterval(function(){
            /** do what you wish **/
            console.log(self.event);
        }, 250);
        return this;
    },
    stop: function(){
        $(document).unbind("mousemove", this.event);
        clearInterval(this.interval);
    },
}).init();
mekwall
  • 28,614
  • 6
  • 75
  • 77
0

Why not use setInterval() instead of timeouts?