23

im generating a function where it needs to set easy and fast a signature. I'm writing the signature in an canvas field. I use jQuery for it, but the refresh rate of mousemove coordinates is not fast enough. What happens is that if you write your signature to fast, you see some white spaces between writed pixels.

How can I set the refresh speed of the mousemove faster?

$("#xx").mousemove(function(e){

    ctx.fillRect(e.pageX - size, e.pageY - size, size, size);

    $("#pagex").html(e.pageX - size);
    $("#pagey").html(e.pageY - size);

}
Vincent
  • 257
  • 1
  • 2
  • 4
  • I think, there is no need to clear the canvas during mouse move for drawing a signature. You just need to draw a line between last mouse location to current mouse location on the canvas. – Kira Jan 06 '17 at 05:28
  • you need to use a binary search to speed it up. – chovy Apr 20 '23 at 09:22

6 Answers6

18

You can't. The mousemove events are generated by the browser, and thus you are receiving them as fast as the browser is generating them.

The browser is not obliged to generate the events at any given rate (either by pixels moved, or by time elapsed): if you move the mouse quickly, you will see that a "jump" in coordinates is reported, as the browser is reporting "the mouse has moved, and it is now here", not "...and went through these pixels". In fact, a browser on a slow computer might generate fewer mousemove events, lest the page slow down to a crawl.

What you could do is to connect successive positions from mousemove events with a straight line - this will obviously not get you any more precision, but it may mitigate the impact.

Piskvor left the building
  • 91,498
  • 46
  • 177
  • 222
8

You need to make your handler faster.

Browsers can drop events if a handler for that event is still running, so you need to get out of the mousemove handler asap. You could try to optimise the code there or defer work till after the mouse movement is complete. Drawing is probably the slowest thing you are doing, so you could store the mouse movements in memory and draw later. This would not update the display until drawing had finished but it would otherwise work better.

river
  • 1,508
  • 1
  • 12
  • 17
7

I would suggest (detailing @river's answer):

  1. in mousemove event handler just save those points (mouse moves through) into some buffer (array) making your event handler as fast as possible
  2. make another function which will read those points from buffer and draw it on canvas as lineTo() -> lineTo() -> lineTo() so all points will be connected, no whitespace between them. Every time you call this function, it will read few points from buffer and draw a line between them. (don't forget to delete them from buffer after drawing)
  3. assign this drawing function into a setInterval() so the drawing of your signature will not wait until user finished "drawing", but it will draw that signature with some slight delay after user's finger movements
Enriqe
  • 567
  • 1
  • 6
  • 22
  • I'd modify this to do the drawing function within a `requestAnimationFrame` loop, but haven't tested to see how that might work – EoghanM Mar 16 '19 at 15:56
4

Some other answers suggested that it's because of a slow handler function. In my tests it didn't make any difference whether I just had count++ in the handler, or much more expensive canvas draw calls - the number of events generated in 10 seconds was about 500 in both cases. However, it might have made a difference on a slower computer.

Apparently most mice/pointers only report their position to the OS fewer than 100 times per second, so this may be something that's not even in the browser's control.

You may want to look into the new PointerEvent.getCoalescedEvents() method. From the MDN docs:

The getCoalescedEvents() method of the PointerEvent interface returns a sequence of all PointerEvent instances that were coalesced into the dispatched pointermove event.

Here's an example:

window.addEventListener("pointermove", function(event) {
  let events = event.getCoalescedEvents();
  for(let e of events) {
    draw(e.pageX, e.pageY);
  }
});

However, after testing this, it only rarely seems to coalesce the events on my computer. Again, though, it may be more useful on slower computers. So for now the best approach is probably to use ctx.lineTo, or a similar method (arcTo, perhaps). Here's a simple working canvas drawing demo that combines getCoalescedEvents with lineTo:

<canvas id="canvas" style="touch-action:none; width:100vw; height:100vh; position:fixed; top:0; left:0; right:0; bottom:0;"></canvas>

<script>
  let mouseIsDown = false;

  let ctx = canvas.getContext("2d");
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;


  window.addEventListener("pointerdown", function(e) {
    ctx.beginPath();
    ctx.moveTo(e.pageX, e.pageY);
    mouseIsDown = true;
  });
  window.addEventListener("pointerup", function(e) {
    mouseIsDown = false;
  });
  window.addEventListener("pointermove", function(event) {
   if(mouseIsDown) {
      let events = event.getCoalescedEvents();
      for(let e of events) {
        ctx.lineTo(e.pageX, e.pageY);
        ctx.stroke();
        ctx.beginPath();
        ctx.moveTo(e.pageX, e.pageY);
      }
   }
  });
</script>
vageko4924
  • 111
  • 4
2

You can fire your own event based on timer, probably a bad idea but better then nothing if you really need it.

User18165
  • 115
  • 9
2

Have you tried using a passive: true and capture: true listener? Usually browsers wait 50-200 ms for a preventDefault() call, but using the passive: true option will get rid of that behavior, at the cost of losing preventDefault(). This lag is why @vageko4924 saw about 500 events total in 10 seconds despite how efficient the handler was. The capture: true option just ensures your callback is fired before all others - this protects you from intermittent lag from slow callbacks.

I'm not sure what this would look like in jQuery, but here it is in vanilla JS:

let x = document.querySelector('#xx'); // which would be faster if it were using getElementById()
x.addEventListener('mousemove', e => {
    // Your logic here
}, {passive: true, capture: true});

Source: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

Nate Symer
  • 2,185
  • 1
  • 20
  • 27