0

Reproduction: focus your mouse on canvas , then spin your mouse in circles for ~15 sec. At first you'll notice how things are smooth. After some time it starts to lose its smoothness and becomes really laggy.

Part of the js function came from the following answer

Make moving Rect more smooth

var canvas = document.getElementById('canvas');
var ctx = document.getElementById('canvas').getContext('2d');

var x;
var y;

var tx = tx || 0;
var ty = ty || 0;

var xDir;
var yDir;

function followMouse(e) {
  x = e.offsetX;
  y = e.offsetY;
  moveObject();
}

function moveObject() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  var scale =  0.2 * Math.max(canvas.width, canvas.height);
  xDir = 0;
  yDir = 0;
  xDir = (x - tx) / scale;
  yDir = (y - ty) / scale;
  tx = tx != x ? tx + xDir : tx;
  ty = ty != y ? ty + yDir : ty;
  ctx.fillRect(tx - 25, ty + 25, 50, 10);
  if (tx != x || ty != y) {
    window.requestAnimationFrame(moveObject);
  }
}

function resizeCanvas() {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
};

canvas.addEventListener('mousemove', _.throttle(function(e) {
  followMouse(e);
}, 30));

window.addEventListener('resize', resizeCanvas, false);

resizeCanvas();
html,
body {
  margin: 0;
  height: 100%;
}

canvas {
  display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<canvas id="canvas"></canvas>
Community
  • 1
  • 1
Paran0a
  • 3,359
  • 3
  • 23
  • 48
  • Is it lagging, or is the rectangle just getting too close the the mouse that the movement no longer seems fluid? – noahnu May 02 '16 at 13:10
  • 1
    `moveObject` starts a loop, and each time you move the mouse, you start a new `moveObject` loop. This means that you will end up with thousands of loops in few seconds... – Fez Vrasta May 02 '16 at 13:15
  • @FezVrasta So what should I do to "destroy" it and start a new one. – Paran0a May 02 '16 at 13:19
  • I would put `cancelAnimationFrame` inside the followMouse function – Fez Vrasta May 02 '16 at 13:27
  • Well you're are starting a new loop at every mousemove (which may happen 100 times per seconds on some devices), so luckily enough, you do throttle it, but still, there are as much loops as non-throttled events : a lot. Why do you make a loop of it btw? – Kaiido May 02 '16 at 14:39
  • @Kaiido I kinda copied that philosophy from here https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations – Paran0a May 02 '16 at 14:49
  • Yes, that's the good way you start an animation loop. But you are starting a new one at each mousemove event. That's bad. – Kaiido May 02 '16 at 15:04
  • So I should just cancel it like Fez said? – Paran0a May 02 '16 at 15:13
  • or not start it at all – Kaiido May 02 '16 at 16:14
  • I dont think I can do what I want without it. I want the rect to follow my mouse with a delay. I dont think thats possible without this. – Paran0a May 02 '16 at 16:28

1 Answers1

1

This happens as for each mousemove a new loop is started. These loops will accumulate and eventually slow things down.

To solve you can implement cancelAnimationFrame() by doing:

...
var timer;

function followMouse(e) {
  x = e.offsetX;
  y = e.offsetY;
  cancelAnimationFrame(timer);
  moveObject();
}

Then store timer reference in the main loop:

...
timer = requestAnimationFrame(moveObject);

This will abort the current request for frame update and allow you to start a new loop without accumulating calls.

For this reason you would also have to initialize x and y since they are not initialized otherwise until the mouse has been moved (which is of course no guarantee).

var x = 0;
var y = 0;

Note: A side-effect of this correction is that now the movement is only calculated once per frame. When accumulated the movement got calculated many times per frame. To compensate adjust the scale to a lower value (shown below).

Modified example

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var x = 0;
var y = 0;

var tx = tx || 0;
var ty = ty || 0;

var xDir;
var yDir;

var timer;

function followMouse(e) {
  x = e.clientX;
  y = e.clientY;
  cancelAnimationFrame(timer);
  moveObject();
}

function moveObject() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  var scale = 0.02 * Math.max(canvas.width, canvas.height);
  xDir = 0;
  yDir = 0;
  xDir = (x - tx) / scale;
  yDir = (y - ty) / scale;
  tx = tx != x ? tx + xDir : tx;
  ty = ty != y ? ty + yDir : ty;
  ctx.fillRect(tx - 25, ty + 25, 50, 10);
  if (tx != x || ty != y) {
    timer = requestAnimationFrame(moveObject);
  }
}

function resizeCanvas() {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
};

canvas.addEventListener('mousemove', _.throttle(function(e) {
  followMouse(e);
}, 30));

window.addEventListener('resize', resizeCanvas, false);

resizeCanvas();
html,
body {
  margin: 0;
  height: 100%;
}

canvas {
  display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<canvas id="canvas"></canvas>
  • Thank you, I understand now. I've noticed I actually never get to have tx=x and ty=y. Which is bad cause I wan't to have a case where object get's to the position of the mouse. You've also slowed down the object? – Paran0a May 03 '16 at 07:21
  • @Paran0a the slow down is simply because the loop executes one/frame. When accumulated the calculations happened many times per frame. You can compensate by adjusting the scale value. –  May 03 '16 at 07:22