Mouse Events

It is possible to detect mouse and keyboard events for a canvas (or any other HTML element). There are seven mouse events:

click
The event occurs when the mouse is clicked on the canvas.
dblclick
The event occurs when the mouse is double-clicked on the canvas.
mousemove
The event occurs when the mouse is moved while it is over the canvas.
mouseover
The event occurs when the mouse is moved onto the canvas.
mouseout
The event occurs when the mouse is moved out of the canvas.
mousedown
The event occurs when any mouse button is pressed over the canvas.
mouseup
The event occurs when any mouse button is released over the canvas.

We need to insert two pieces of code in order to use a mouse event.

We need to associate the event function with the canvas. This is done by adding an event listener to the canvas.

canvas.addEventListener('click', function(e){});

The variable, 'e' is provided by the system. It provides access to the x and y location of the screen pixel that was clicked. To get the canvas pixel location, we need to subtract the canvas top left corner x and y value from the screen x and y. The example below gets the correct canvas x and y location.

Example showing how to get the canvas x and y position (Run Example).
Note that this example does not need to use the framework classes, as nothing is being drawn on the canvas!

<!-- Author: Derek O Reilly, Dundalk Institute of Technology, Ireland. -->

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>GENIUS worked example</title>
        <link rel="shortcut icon" type="image/png" href="images/genius_icon.png"/>
        <link href="css/game.css" rel="stylesheet" type="text/css"/>

        <style>
            #gameCanvas
            {
                margin-top:100px;
                margin-left: 100px;
            }
        </style>
        
        <script>
            let mouseX = 0;
            let mouseY = 0;

            window.onload = function ()
            {
                let canvas = document.getElementById("gameCanvas");
                canvas.width = canvas.clientWidth;
                canvas.height = canvas.clientHeight;
                let ctx = canvas.getContext("2d");

                canvas.addEventListener('click', function (e)
                {
                    if (e.which === 1)
                    {
                        let canvasBoundingRectangle = canvas.getBoundingClientRect();
                        mouseX = e.clientX - canvasBoundingRectangle.left;
                        mouseY = e.clientY - canvasBoundingRectangle.top;

                        alert("x:" + mouseX + "    y:" + mouseY);
                    }
                });
            };
        </script>    
    </head>

    <body>
        <canvas id = "gameCanvas" tabindex="1"></canvas>
        <p>Click the mouse on the canvas to get the mouse x,y position within the canvas.<br>
            The coordinates will still work after the browser has been resized. Try resizing the browser to test this.<br>
            Note that this canvas is 500 by 500 pixels.</p>
    </body>
</html>

The canvas is moved away from the document top-left corner for this example, so as to show that our code can detect the mouse coordinates inside the canvas.

        <style>
            #gameCanvas
            {
                margin-top:100px;
                margin-left: 100px;
            }
        </style>


A mouse click handler is added to the canvas.

                canvas.addEventListener('click', function (e)
                {
                    if (e.which === 1)
                    {
                        let canvasBoundingRectangle = canvas.getBoundingClientRect();
                        mouseX = e.clientX - canvasBoundingRectangle.left;
                        mouseY = e.clientY - canvasBoundingRectangle.top;

                        alert("x:" + mouseX + "    y:" + mouseY);
                    }
                });   


If the left mouse button has been clicked, then e.which will hold the value 1.

            canvas.addEventListener('click', function (e)
            {
                if (e.which === 1)
                {
               
                }
            }


The mouse click returns the screen x and y values rather than the canvas values. The highlighted code below converts from screen to canvas coordinates.

            canvas.addEventListener('click', function (e)
            {
                if (e.which === 1)
                {
                    let canvasBoundingRectangle = canvas.getBoundingClientRect();
                    mouseX = e.clientX - canvasBoundingRectangle.left;
                    mouseY = e.clientY - canvasBoundingRectangle.top;

                    alert("x:" + mouseX + "    y:" + mouseY);
                }
            }

 

In the example below, an image is drawn at the location of a mouse click.

Example of a mouse click event class (Run Example)

event_mouse_click.js

/* Author: Derek O Reilly, Dundalk Institute of Technology, Ireland.             */
/* There should always be a javaScript file with the same name as the html file. */
/* This file always holds the playGame function().                               */
/* It also holds game specific code, which will be different for each game       */





/******************** Declare game specific global data and functions *****************/
/* images must be declared as global, so that they will load before the game starts  */



/******************* END OF Declare game specific data and functions *****************/







/* Always have a playGame() function                                     */
/* However, the content of this function will be different for each game */
function playGame()
{
    /* We need to initialise the game objects outside of the Game class */
    /* This function does this initialisation.                          */
    /* This function will:                                              */
    /* 1. create the various game game gameObjects                   */
    /* 2. store the game gameObjects in an array                     */
    /* 3. create a new Game to display the game gameObjects          */
    /* 4. start the Game                                                */


    /* Create the various gameObjects for this game. */
    /* This is game specific code. It will be different for each game, as each game will have it own gameObjects */

    gameObjects[0] = new EventMouseClick();   

    /* END OF game specific code. */


    /* Always create a game that uses the gameObject array */
    let game = new CanvasGame();

    /* Always play the game */
    game.start();
    
    /* add event listners for input changes */
    document.addEventListener("click", function (e)
    {
        let canvasBoundingRectangle = document.getElementById("gameCanvas").getBoundingClientRect();
        let mouseX = e.clientX - canvasBoundingRectangle.left;
        let mouseY = e.clientY - canvasBoundingRectangle.top;
        
        gameObjects[0].setX(mouseX);
        gameObjects[0].setY(mouseY);
    });
}

In the file "event_mouse_click.js", whenever the user clicks the mouse, we convert it from screen coordinates to canvas coordinates (shown in red). We then pass the canvas coordinates to the the gameObject (shown in blue).

    /* add event listners for input changes */
    document.addEventListener("click", function (e)
    {
        let canvasBoundingRectangle = document.getElementById("gameCanvas").getBoundingClientRect();
        let mouseX = e.clientX - canvasBoundingRectangle.left;
        let mouseY = e.clientY - canvasBoundingRectangle.top;
        
        gameObjects[0].setX(mouseX);
        gameObjects[0].setY(mouseY);
    });

In the file "EventMouseClick.js", we use two setter methods to set the position of the gameObject whenever the user clicks the mouse. In the code below, this.x and this.y are the top-left corner of the gameObject.

EventMouseClick.js

/* Author: Derek O Reilly, Dundalk Institute of Technology, Ireland. */

class EventMouseClick extends GameObject
{
    /* Each gameObject MUST have a constructor() and a render() method.        */
    /* If the object animates, then it must also have an updateState() method. */

    constructor()
    {
        super(null); /* as this class extends from GameObject, you must always call super() */

        /* These variables depend on the object */
        this.width = 100;
        this.height = 100;
        this.x = 100;
        this.y = 100;

    }

    render()
    {
        ctx.fillStyle = 'black';
        ctx.fillRect(this.x, this.y, this.width, this.height);
    }

    setX(newCentreX)
    {
        this.x = newCentreX - (this.width / 2);
    }

    setY(newCentreY)
    {
        this.y = newCentreY - (this.height / 2);
    }
}

The black rectangle has its centre set to be the new x and y positions, as shown below.

    setX(newCentreX)
    {
        this.x = newCentreX - (this.width / 2);
    }

    setY(newCentreY)
    {
        this.y = newCentreY - (this.height / 2);
    }

Write code to draw an image in a new location whenever the user clicks on the canvas, as shown in this link.

Dragging an Image

Example that detects if the mouse is inside an image and takes the x and y offsets into account when dragging an image (Run Example)

drag_image.js

&/* Author: Derek O Reilly, Dundalk Institute of Technology, Ireland.             */
/* There should always be a javaScript file with the same name as the html file. */
/* This file always holds the playGame function().                               */
/* It also holds game specific code, which will be different for each game       */





/******************** Declare game specific global data and functions *****************/
/* images must be declared as global, so that they will load before the game starts  */
let beachImage = new Image();
beachImage.src = "images/beach.png";
/******************* END OF Declare game specific data and functions *****************/







/* Always have a playGame() function                                     */
/* However, the content of this function will be different for each game */
function playGame()
{
    /* We need to initialise the game objects outside of the Game class */
    /* This function does this initialisation.                          */
    /* This function will:                                              */
    /* 1. create the various game game gameObjects                   */
    /* 2. store the game gameObjects in an array                     */
    /* 3. create a new Game to display the game gameObjects          */
    /* 4. start the Game                                                */


    /* Create the various gameObjects for this game. */
    /* This is game specific code. It will be different for each game, as each game will have it own gameObjects */

    gameObjects[0] = new DragImage(beachImage);

    /* END OF game specific code. */

    /* Always create a game that uses the gameObject array */
    let game = new CanvasGame();

    /* Always play the game */
    game.start();

    /* add event listners for input changes */
    document.addEventListener("mousedown", function (e)
    {
        if (e.which === 1)  // left mouse button
        {
            let canvasBoundingRectangle = document.getElementById("gameCanvas").getBoundingClientRect();
            let mouseX = e.clientX - canvasBoundingRectangle.left;
            let mouseY = e.clientY - canvasBoundingRectangle.top;
            
            if (gameObjects[0].pointIsInsideBoundingRectangle(mouseX, mouseY))
            {
                gameObjects[0].setOffsetX(mouseX);
                gameObjects[0].setOffsetY(mouseY);
            }
        }
    });
    
    document.addEventListener("mousemove", function (e)
    {
        if (e.which === 1)  // left mouse button
        {
            let canvasBoundingRectangle = document.getElementById("gameCanvas").getBoundingClientRect();
            let mouseX = e.clientX - canvasBoundingRectangle.left;
            let mouseY = e.clientY - canvasBoundingRectangle.top;
            
            if (gameObjects[0].pointIsInsideBoundingRectangle(mouseX, mouseY))
            {
                gameObjects[0].setX(mouseX);
                gameObjects[0].setY(mouseY);
            }
        }
    });
}

The mousedown event handler function is used to initialise the offset of the mouse from the top-left corner of the image. This is needed to ensure that the dragging of the image is smooth. Without this, the image top-left corner would jump to the current mouse position when we start to drag an image.

The mousemove event handler function is used to drag the image.

If we need to detect if the mouse is inside a GameObject, we need to write a method within the GameObject class to deal with this. We can call the method, as shown below:

if (gameObjects[0].pointIsInsideBoundingRectangle(mouseX, mouseY))

DragImage.js

/* Author: Derek O Reilly, Dundalk Institute of Technology, Ireland. */

class DragImage extends GameObject
{
    /* Each gameObject MUST have a constructor() and a render() method.        */
    /* If the object animates, then it must also have an updateState() method. */

    constructor(image)
    {
        super(null); /* as this class extends from GameObject, you must always call super() */

        /* These variables depend on the object */
        this.image = image;
        this.width = 100;
        this.height = 100;
        this.x = 100;
        this.y = 100;
        this.offsetX = 0;
        this.offsetY = 0;
    }

    render()
    {
        ctx.drawImage(this.image, this.x, this.y, this.width, this.height);
        ctx.strokeStyle = 'black';
        ctx.strokeRect(this.x - 1, this.y - 1, this.width + 2, this.height + 2);
    }

    setX(newMouseX)
    {
        this.x = newMouseX - this.offsetX;
    }

    setY(newMouseY)
    {
        this.y = newMouseY - this.offsetY;
    }

    setOffsetX(newMouseX)
    {
        this.offsetX = newMouseX - this.x;
    }

    setOffsetY(newMouseY)
    {
        this.offsetY = newMouseY - this.y;
    }

    pointIsInsideBoundingRectangle(pointX, pointY)
    {
        if ((pointX > this.x) && (pointY > this.y))
        {
            if (pointX > this.x)
            {
                if ((pointX - this.x) > this.width)
                {
                    return false; // to the right of this gameObject
                }
            }

            if (pointY > this.y)
            {
                if ((pointY - this.y) > this.height)
                {
                    return false; // below this gameObject
                }
            }
        }
        else // above or to the left of this gameObject
        {
            return false;
        }
        return true; // inside this gameObject
    }
}

Before we can drag an image, we need to be able to detect:

  1. if the mouse is inside the image
  2. the mouse offset position relative to the top-left corner of the image

The code below will detect if the location (x, y) is positioned inside an image.

DragImage.js

pointIsInsideBoundingRectangle(pointX, pointY)
    {
        if ((pointX > this.x) && (pointY > this.y))
        {
            if (pointX > this.x)
            {
                if ((pointX - this.x) > this.width)
                {
                    return false; // to the right of this gameObject
                }
            }

            if (pointY > this.y)
            {
                if ((pointY - this.y) > this.height)
                {
                    return false; // below this gameObject
                }
            }
        }
        else // above or to the left of this gameObject
        {
            return false;
        }
        return true; // inside this gameObject
    }

 

The offset needs to be calculated when the mouse is pressed down on an image. The code below will calculate the offsetX and offsetY of the mouse within an image.

drag_image.js

    document.addEventListener("mousedown", function (e)
    {
        if (e.which === 1)  // left mouse button
        {
            let canvasBoundingRectangle = document.getElementById("gameCanvas").getBoundingClientRect();
            let mouseX = e.clientX - canvasBoundingRectangle.left;
            let mouseY = e.clientY - canvasBoundingRectangle.top;
            
            if (gameObjects[0].pointIsInsideBoundingRectangle(mouseX, mouseY))
            {
                gameObjects[0].setOffsetX(mouseX);
                gameObjects[0].setOffsetY(mouseY);
            }
        }
    });

Whenever an image changes position, the offset needs to be taken into account when calculating the new image top-left x and y positions. This is done by subtracting the offset values that were calculated in the mousedownHandler(e) code above.

DragImage.js

    setOffsetX(newMouseX)
    {
        this.offsetX = newMouseX - this.x;
    }

    setOffsetY(newMouseY)
    {
        this.offsetY = newMouseY - this.y;
    }

Write code to make a drawing tool that is similar to the one shown at this link. Hint: Use two gameObjects, one each for the image and the scribble and use an offscreen canvas for the Scribble

Mouse Wheel Event

We can associate a function with the mouse wheel using the code below. Unfortunately, Firefox deals with the mousewheel differently to other browsers, so we must add a separate handler for it.

// IE, Chrome, Safari, Opera
document.getElementById('gameCanvas').addEventListener("mousewheel", mouseWheelHandler, false);

// Firefox
document.getElementById('gameCanvas').addEventListener("DOMMouseScroll", mouseWheelHandler, false);

The function is provided with a system variable, e. This variable e.wheelDelta contains information relating to the mouse wheel. This value increments/decrements in units of 120. Therefore, we need to devide it by 120 to get a unit increment/decrement value.

function mouseWheelHandler(e) 
{
unitChange = e.wheelDelta / 120; // unitChange will be equal to either +1 or -1
// code to use the unitChange value is placed below }

Example using the mouse wheel to scale an image (Run Example). In this example, the image will only scale if the mouse is over the image.

scale_image.js

/* Author: Derek O Reilly, Dundalk Institute of Technology, Ireland.             */
/* There should always be a javaScript file with the same name as the html file. */
/* This file always holds the playGame function().                               */
/* It also holds game specific code, which will be different for each game       */





/******************** Declare game specific global data and functions *****************/
/* images must be declared as global, so that they will load before the game starts  */
let beachImage = new Image();
beachImage.src = "images/beach.png";
/******************* END OF Declare game specific data and functions *****************/







/* Always have a playGame() function                                     */
/* However, the content of this function will be different for each game */
function playGame()
{
    /* We need to initialise the game objects outside of the Game class */
    /* This function does this initialisation.                          */
    /* This function will:                                              */
    /* 1. create the various game game gameObjects                   */
    /* 2. store the game gameObjects in an array                     */
    /* 3. create a new Game to display the game gameObjects          */
    /* 4. start the Game                                                */


    /* Create the various gameObjects for this game. */
    /* This is game specific code. It will be different for each game, as each game will have it own gameObjects */

    gameObjects[0] = new ScaleImage(beachImage);

    /* END OF game specific code. */

    /* Always create a game that uses the gameObject array */
    let game = new CanvasGame();

    /* Always play the game */
    game.start();

    /* add event listners for input changes */
    // IE, Chrome, Safari, Opera
    document.getElementById('gameCanvas').addEventListener("mousewheel", mouseWheelHandler, false);
    // Firefox
    document.getElementById('gameCanvas').addEventListener("DOMMouseScroll", mouseWheelHandler, false);

    function mouseWheelHandler(e)
    {
        let canvasBoundingRectangle = document.getElementById("gameCanvas").getBoundingClientRect();
        let mouseX = e.clientX - canvasBoundingRectangle.left;
        let mouseY = e.clientY - canvasBoundingRectangle.top;

        if (gameObjects[0].pointIsInsideBoundingRectangle(mouseX, mouseY))
        {
            unitChange = e.wheelDelta / 120;
            gameObjects[0].changeWidthAndHeight(unitChange);
        }
    }
}

ScaleImage.js

/* Author: Derek O Reilly, Dundalk Institute of Technology, Ireland. */

class ScaleImage extends GameObject
{
    /* Each gameObject MUST have a constructor() and a render() method.        */
    /* If the object animates, then it must also have an updateState() method. */

    constructor(image)
    {
        super(null); /* as this class extends from GameObject, you must always call super() */
        /* These variables depend on the object */
        this.image = image;
        this.x = 200;
        this.y = 200;
        this.width = 100;
        this.height = 100;
    }

    render()
    {
        ctx.drawImage(this.image, this.x, this.y, this.width, this.height);
        ctx.strokeStyle = 'black';
        ctx.strokeRect(this.x - 1, this.y - 1, this.width + 2, this.height + 2);
    }

    changeWidthAndHeight(sizeChange)  // note that stepSize will be negative for scaling down
    {
        this.width += sizeChange; 
        this.height += sizeChange;
        
        /* Scaling is about the centre of the image, so adust the x and y position to match new size */
        this.x -= sizeChange / 2;
        this.y -= sizeChange / 2;
    }

    pointIsInsideBoundingRectangle(pointX, pointY)
    {
        if ((pointX > this.x) && (pointY > this.y))
        {
            if (pointX > this.x)
            {
                if ((pointX - this.x) > this.width)
                {
                    return false; // to the right of this gameObject
                }
            }

            if (pointY > this.y)
            {
                if ((pointY - this.y) > this.height)
                {
                    return false; // below this gameObject
                }
            }
        }
        else // above or to the left of this gameObject
        {
            return false;
        }
        return true; // inside this gameObject
    }
}

The image has its width and height changed by the amount sizeChange. The image's x and y coordinates are adjusted to ensure that the scaling happens about the centre of the image.

    changeWidthAndHeight(sizeChange)  // note that stepSize will be negative for scaling down
    {
        this.width += sizeChange; 
        this.height += sizeChange;
        
        /* Scaling is about the centre of the image, so adust the x and y position to match new size */
        this.x -= sizeChange / 2;
        this.y -= sizeChange / 2;
    }


The pointIsInsideBoundingRectangle() method ensures that scaling only occurs if the mouse if inside the image when the user attempts to scale.

    pointIsInsideBoundingRectangle(pointX, pointY)
    {
        ...
    }

 

Write code to move, drag and scale an image, as shown in this link.