Tutorials Books Videos Forums

Change the theme! Search!
Rambo ftw!

Customize Theme


Color

Background


Done

Table of Contents

Working with the Mouse

by kirupa   |   filed under Working with the Canvas

When we talk about interactivity on the canvas, we can't go really far without talking about everyone's favorite pointing device...the mouse:

There are so many uses for the mouse on the canvas. You can use the mouse to draw, move things around, use it as part of a game, and more. I will warn you, though. This tutorial is an introduction to the mechanics of working with the mouse. It's a bit boring, but you need to suffer through this in order to get to the much cooler things we'll look at directly after this. Think of this tutorial as the bowl of vegetables you need to go through before getting dessert.

Onwards!

The Mouse and the Canvas

Often times, the canvas seems like its own mysterious beast. It is not until you run into areas like events that aren't specific to the canvas where you realize that all of this is just a small part of this larger JavaScript enclosure. For the next few sections, you are going to be learning general JavaScript 101 stuff about events, so feel free to skip or gloss over the material if you are intimately familiar with dealing with mouse events.

Meet the Events

Our canvas element works with all the mouse events that your browser exposes:

What these events do should be somewhat easy to figure out given their name, so I won't bore you with details about that. Let's just move on to the next section where we learn how to listen to them.

Listening to and Handling Mouse Events

To listen for these events on your canvas element, use the addEventListener method and specify the event you are listening for and the event handler to call when that event is overheard:

var canvas = document.querySelector("#myCanvas");
var context = canvas.getContext("2d");

canvas.addEventListener("mousemove", doSomething, false);

That is pretty straightforward. In the highlighted line, we are listening for the mousemove event. When that event gets overheard, the doSomething function (aka the event handler) gets called. The last argument specifies whether we want our event to be captured in the bubbling phase or not. We won't focus on this argument. Instead, let's take another look at the event handler...

The event handler is responsible for reacting to an event once it is overheard. Basically, it's pretty important...and totally a big deal in the scene that is your code. Under the covers, an event handler is nothing more than a function with an argument for getting at what is known as the event arguments:

function doSomething(e) {
  // do something interesting
  
}

That detail is important. This event argument is the extremely awesome MouseEvent object that gives you access to a bunch of useful properties about the event that you just fired - properties that contains information such as the mouse's position, the button that was pressed, and more. We are going to devote the next few sections looking deeper into some of these useful properties.

The Global Mouse Position

The first MouseEvent properties we will look at are the screenX and screenY properties that return the distance your mouse cursor is from the top-left location of your primary monitor:

the global screen position

Here is a very simple example of the screenX and screenY properties at work:

canvas.addEventListener("mousemove", mouseMoving, false);

function mouseMoving(e) {
    console.log(e.screenX + " " + e.screenY);
}

It doesn't matter what other margin/padding/offset/layout craziness you may have going on in your page. The values returned are always going to be the distance between where your mouse is now and where the top-left corner of your primary monitor is.

The Mouse Position Inside the Browser

This is probably the part of the MouseEvent object that you will run into over and over again. The clientX and clientY properties return the x and y position of the mouse relative to your browser's (technically, the browser viewport's) top-left corner:

relative to the browser

The code for this is nothing exciting:

var canvas = document.querySelector("#myCanvas");
canvas.addEventListener("mousemove", mouseMoving, false);

function mouseMoving(e) {
    console.log(e.clientX + " " + e.clientY);
}

We just call the clientX and clientY properties via the MouseEvent argument to see some sweet pixel values returned as a result. What these values don't advertise is that you often need to do a little extra bit of additional calculation to get the exact mouse position inside our canvas. That calculation isn't hard, and we will look at that in a few sections!

Detecting Which Button was Clicked

Your mice often have multiple buttons or ways to simulate multiple buttons. The most common button configuration involves a left button, a right button, and a middle button (often a click on your mouse wheel). To figure out which mouse button was pressed, you have the button property. This property returns a 0 if the left mouse button was pressed, a 1 if the middle button was pressed, and a 2 if the right mouse button was pressed:

the three buttons that were pressed

The code for using the button property to check for which button was pressed looks exactly as you would expect:

canvas.addEventListener("mousedown", buttonPress, false);

function buttonPress(e) {
    if (e.button == 0) {
        console.log("Left mouse button pressed!");
    } else if (e.button == 1) {
        console.log("Middle mouse button pressed!");
    } else if (e.button == 2) {
        console.log("Right mouse button pressed!");
    } else {
        console.log("Things be crazy up in here!!!");
    }
}

In addition to the button property, you also have the buttons and which properties that sorta do similar things to help you figure out which button was pressed. I'm not going to talk too much about those two properties, but just know that they exist.

Getting the Exact Mouse Position

Following up on a thread we left unexplored a few moments ago, the clientX and clientY properties don't give you the exact mouse position in many situations. Understanding the details of why is something that I cover in this article, but the gist of it is that the clientX and clientY properties don't account for your canvas element physically being pushed around by all of its ancestors. A margin or padding here and there can cause your positioning inside the canvas element to go out-of-sync. It's really quite sad.

The solution is to pair up your clientX and clientY properties with the following code (taken from the Get an Element's Position Using JavaScript article) that takes into account all of the various position-related shenanigans that your canvas might be affected by:

// Helper function to get an element's exact position
function getPosition(el) {
  var xPos = 0;
  var yPos = 0;

  while (el) {
    if (el.tagName == "BODY") {
      // deal with browser quirks with body/window/document and page scroll
      var xScroll = el.scrollLeft || document.documentElement.scrollLeft;
      var yScroll = el.scrollTop || document.documentElement.scrollTop;

      xPos += (el.offsetLeft - xScroll + el.clientLeft);
      yPos += (el.offsetTop - yScroll + el.clientTop);
    } else {
      // for all other non-BODY elements
      xPos += (el.offsetLeft - el.scrollLeft + el.clientLeft);
      yPos += (el.offsetTop - el.scrollTop + el.clientTop);
    }

    el = el.offsetParent;
  }
  return {
    x: xPos,
    y: yPos
  };
}

Here is an example of this code in action inside our canvas element:

var canvas = document.querySelector("#myCanvas");
var context = canvas.getContext("2d");
var canvasPosition = getPosition(canvas);

canvas.addEventListener("mousemove", doSomething, false);

// take into account page scrolls and resizes
window.addEventListener("scroll", updatePosition, false);
window.addEventListener("resize", updatePosition, false);

function updatePosition() {
  canvasPosition = getPosition(canvas);
}

function doSomething(e) {
  // get the exact mouse X and Y coordinates
  var mouseX = e.clientX - canvasPosition.x;
  var mouseY = e.clientY - canvasPosition.y;

  // print it to the console
  console.log("The mouse position is: " + mouseX + ", " + mouseY);
}

The canvasPosition variable stores the object returned by the getPosition function, and the mouseX and mouseY variables store the exact position once you combine canvasPosition's result with the clientX and clientY value:

var mouseX = e.clientX - canvasPosition.x;
var mouseY = e.clientY - canvasPosition.y;

For completeness, we even listen to the window resize and scroll events to update the value stored by canvasPosition:

// take into account page scrolls and resizes
window.addEventListener("scroll", updatePosition, false);
window.addEventListener("resize", updatePosition, false);

function updatePosition() {
  canvasPosition = getPosition(canvas);
}	

We do this to ensure that our mouse position values remain accurate even if users scroll the page or resize the browser window. All of the code you see here might be going away in the future, though. There is a scheme underway to add an offsetX and offsetY property to the MouseEvent object that automatically takes care of all this.

Conclusion

In this tutorial, we just covered the basics of working with the mouse. If there is anything you take out of this, the section on getting the correct mouse position is the one you should keep in mind. It isn't obvious that your clientX and clientY properties aren't entirely adequate for getting your mouse position, and the solution for that isn't very obvious either. Now that you know about this issue, though, you can safely avoid it without running into any sort of problems!

Just a final word before we wrap up. If you have a question and/or want to be part of a friendly, collaborative community of over 220k other developers like yourself, post on the forums for a quick response!

Kirupa's signature!

The KIRUPA Newsletter

Thought provoking content that lives at the intersection of design 🎨, development 🤖, and business 💰 - delivered weekly to over a bazillion subscribers!

SUBSCRIBE NOW

Creating engaging and entertaining content for designers and developers since 1998.

Follow:

Popular

Loose Ends

:: Copyright KIRUPA 2024 //--