Mouse interaction, mouse events

Mouse interaction, mouse events

INTRODUCTION

Detecting mouse events in a canvas is quite straightforward: you add an event listener to the canvas, and the browser invokes that listener when the event occurs.

  1. canvas.addEventListener(‘mousedown’, function (evt) {
  2. // do something with to the mousedown event
  3. });

The event received by the listener function will be used for getting the button number or the coordinates of the mouse cursor. Before looking at different examples, let’s look at the different event types we can listen to.

THE DIFFERENT MOUSE EVENTS

We saw in the last example how to detect the mouseenter and mouseout events.

There are other events related to the mouse:

    • mouseleave: similar to mouseout, fired when the mouse leaves the surface of the element. The difference between mouseleave and mouseout is that mouseleave does not fire when the cursor moves over descendant elements, and mouseout is fired when the element moved is outside of the bounds of the original element or is a child of the original element.
    • mouseover: the mouse cursor is moving over the element that listens to that event. A mouseover event occurs on an element when you are over it – coming from either its child OR parent element, but a mouseenter event only occurs when the mouse moves from the parent element to the child element.
    • mousedown: fired when a mouse button is pressed.
    • mouseup: fired when a mouse button is released.
    • mouseclick: fired after a mousedown and a mouseup have occured.
    • mousemove: fired while the mouse moves over the element. Each time the mouse moves, a new event is fired, unlike with mouseover or mouseenter, where only one event is fired.

THE TRICKY PART: ACCURATELY GETTING THE MOUSE POSITION RELATIVE TO THE CANVAS

When you listen to any of the above events, the event object (we call it a “DOM event”), passed to the listener function, has properties that correspond to the mouse coordinates: clientX and clientY.

However, these are what we call “window coordinates”. Instead of being relative to the canvas itself, they are relative to the window (the page).

Most of the time you need to work with the mouse position relative to the canvas, not to the window, so you must convert the coordinates between the window and the canvas. This will take into account the position of the canvas, and the CSS properties that may affect the canvas position (margin, etc.).

Fortunately, there exists a method for getting the position and size of any element in the page: getBoundingClientRect().

The example that shows the problem is at: http://jsbin.com/bekeso/2/edit

WRONG code:

  1. canvas.addEventListener(‘mousemove’, function (evt) {
  2.     mousePos = getMousePos(canvas, evt);
  3.     var message = ‘Mouse position: ‘ + mousePos.x + ‘,’ + mousePos.y;
  4.     writeMessage(canvas, message);
  5. }, false);
  6.  
  7. function getMousePos(canvas, evt) {
  8.    // WRONG!!!
  9.    return {
  10.       x: evt.clientX,
  11.       y: evt.clientY
  12.    };
  13. }

Here is the result, when the mouse is approximately at the top left corner of the canvas:

Good version of the code: http://jsbin.com/miduqu/3/edit

  1. function getMousePos(canvas, evt) {
  2.    // necessary to take into account CSS boundaries
  3.    var rect = canvas.getBoundingClientRect();
  4.    return {
  5.       x: evt.clientX rect.left,
  6.       y: evt.clientY rect.top
  7.    };
  8. }

Result (the cursor is approximately at the top left corner):

mouse at zero zero

GOOD EXAMPLE THAT SHOWS HOW TO DISPLAY THE MOUSE POSITION, AND THE MOUSE BUTTON THAT HAS BEEN PRESSED OR RELEASED

This example uses the previous function for computing the mouse position correctly. It listens to mousemove, mousedown and mouseup events, and shows how to get the mouse button number using the evt.buttonproperty.

Online example: http://jsbin.com/miduqu/2/edit

Extract from source code:

  1. var canvas, ctx, mousePos, mouseButton;
  2.  
  3. window.onload = function init() {
  4.     canvas = document.getElementById(‘myCanvas’);
  5.     ctx = canvas.getContext(‘2d’);
  6.  
  7.     canvas.addEventListener(‘mousemove’, function (evt) {
  8.         mousePos = getMousePos(canvas, evt);
  9.         var message = ‘Mouse position: ‘ + mousePos.x + ‘,’ + mousePos.y;
  10.         writeMessage(canvas, message);
  11.     }, false);
  12.  
  13.     canvas.addEventListener(‘mousedown’, function (evt) {
  14.         mouseButton = evt.button;
  15.         var message = “Mouse button “ + evt.button + ” down at position: “ + mousePos.x + ‘,’ + mousePos.y;
  16.         writeMessage(canvas, message);
  17.     }, false);
  18.  
  19.     canvas.addEventListener(‘mouseup’, function (evt) {
  20.         var message = “Mouse up at position: “ + mousePos.x + ‘,’ + mousePos.y;
  21.         writeMessage(canvas, message);
  22.     }, false);
  23. };
  24.  
  25. function writeMessage(canvas, message) {
  26.    ctx.save();
  27.    ctx.clearRect(0, 0, canvas.width, canvas.height);
  28.    ctx.font = ’18pt Calibri’;
  29.    ctx.fillStyle = ‘black’;
  30.    ctx.fillText(message, 10, 25);
  31.    ctx.restore();
  32. }
  33.  
  34. function getMousePos(canvas, evt) {
  35.    // necessary to take into account CSS boudaries
  36.    var rect = canvas.getBoundingClientRect();
  37.    return {
  38.       x: evt.clientX rect.left,
  39.       y: evt.clientY rect.top
  40.    };
  41. }

EXAMPLE: MOVE THE MONSTER WITH THE MOUSE, ROTATE IT WHEN A MOUSE BUTTON IS PRESSED.

This example shows an animation at 60 frames/s using requestAnimationFrame, were the monster is drawn at the mouse position, and if a mouse button is pressed, the monster starts rotating around its center. If we release the mouse button, the rotation stops.

Online example: http://jsbin.com/pedihokoyu/1/edit

Extract from source code:

  1. var canvas, ctx;
  2. var monsterX=100, monsterY=100, monsterAngle=0;
  3. var incrementX = 0;
  4. var incrementAngle =0;
  5. var mousePos;
  6. function init() {
  7.     …
  8.     // 3bis – Add mouse listeners
  9.     canvas.addEventListener(‘mousemove’, handleMousemove, false);
  10.     canvas.addEventListener(‘mousedown’, handleMousedown, false);
  11.     canvas.addEventListener(‘mouseup’, handleMouseup, false);
  12.  
  13.     // 4 – Start the animation
  14.     requestId = requestAnimationFrame(animationLoop);
  15. }
  16. function handleMousemove(evt) {
  17.     // The mousePos will be taken into account in the animationLoop
  18.     mousePos = getMousePos(canvas, evt);
  19. }
  20. function handleMousedown(evt) {
  21.    // the increment on the angle will be
  22.    // taken into account in the animationLoop
  23.    incrementAngle = 0.1;
  24. }
  25. function handleMouseup(evt) {
  26.     incrementAngle = 0;  // stops the rotation
  27. }
  28. function getMousePos(canvas, evt) {
  29.  … // same as before
  30. }
  31. function animationLoop() {
  32.    // 1 – Clear
  33.    ctx.clearRect(0, 0, canvas.width, canvas.height);
  34.  
  35.    // 2 – Draw
  36.    drawMonster(monsterX, monsterY, monsterAngle, ‘green’, ‘yellow’);
  37.  
  38.    // 3 – Move
  39.    if(mousePos !== undefined) { // test necessary, maybe the mouse is not yet on canvas
  40.       monsterX = mousePos.x;
  41.       monsterY = mousePos.y;
  42.       monsterAngle += incrementAngle;
  43.    }
  44.    …
  45.  
  46.    // call again mainloop after 16.6 ms (60 frames/s)
  47.    requestId = requestAnimationFrame(animationLoop);
  48. }

This example shows one very important good practice when doing animation and interaction: if you want to achieve a smooth animation, set the state variables 60 times/s inside the animation loop (lines 45-49), depending on increments you set in event listeners (lines 23-31).

EXAMPLE: DRAW IN A CANVAS AS IF YOU WERE USING A PENCIL

Online example: http://jsbin.com/bijusa/3/edit

paint in a canvas

Source code:

  1. var canvas, ctx, previousMousePos;
  2. function drawLineImmediate(x1, y1, x2, y2) {
  3.     // a line is a path with a single draw order
  4.     // we need to do this in this example otherwise
  5.     // at each mouse event we would draw the whole path
  6.     // from the beginning. Remember that lines
  7.     // normally are only usable in path mode
  8.     ctx.beginPath();
  9.     ctx.moveTo(x1, y1);
  10.     ctx.lineTo(x2, y2);
  11.     ctx.stroke();
  12. }
  13.  
  14. function handleMouseMove(evt) {
  15.      var mousePos = getMousePos(canvas, evt);
  16.  
  17.      // Let’s draw some lines that follow the mouse pos
  18.      if (!started) {
  19.          previousMousePos = mousePos; // get the current mouse position
  20.          started = true;
  21.      } else {
  22.          // We need to have two consecutive mouse positions before drawing a line
  23.          drawLineImmediate(previousMousePos.x, previousMousePos.y,
  24.                            mousePos.x,         mousePos.y);
  25.          previousMousePos = mousePos;
  26.      }
  27. }
  28.  window.onload = function () {
  29.    …
  30.    started = false;
  31.  
  32.    canvas.addEventListener(‘mousemove’, handleMouseMove, false);
  33. };

We had to define a variable started=false; as we cannot draw any line before the mouse moved (we need at least two consecutive positions). This is done in the test at line 21.

SAME EXAMPLE BUT WE DRAW ONLY WHEN A MOUSE BUTTON IS PRESSED

Online example: http://jsbin.com/lavexi/3/edit

paint when mouse is pressed

We just added mouseup and  mousedown listeners, extract from the source code:

  1. function handleMouseMove(evt) {
  2.      var mousePos = getMousePos(canvas, evt);
  3.  
  4.      // Let’s draw some lines that follow the mouse pos
  5.      if (painting) {
  6.          drawLineImmediate(previousMousePos.x, previousMousePos.y,
  7.                            mousePos.x,         mousePos.y);
  8.          previousMousePos = mousePos;
  9.      }
  10. }
  11. function clicked(evt) {
  12.     previousMousePos = getMousePos(canvas, evt);
  13.     painting = true;
  14. }
  15.  
  16. function released(evt) {
  17.     painting = false;
  18. }
  19.  
  20.  window.onload = function () {
  21.     canvas = document.getElementById(‘myCanvas’);
  22.     ctx = canvas.getContext(‘2d’);
  23.     painting = false;
  24.  
  25.     canvas.addEventListener(‘mousemove’, handleMouseMove, false);
  26.     canvas.addEventListener(‘mousedown’, clicked);
  27.     canvas.addEventListener(‘mouseup’, released);
  28. };
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s