Acknowledgements: Writing this would not have been possible without the excellent documentation I have found in http://www.typescriptgames.com/
The sample document:
To demonstrate the use of the mouse on a canvas, I have created a very simple page:
This page is a primitive drawing program that just draws a line on the canvas till the point where the mouse was clicked.
The html is below:
<!doctype html>
<html>
<head>
<title>Demonstration of Canvas and Typescript</title>
<link rel="stylesheet" href="canvas.css">
</head>
<body>
<header>
<h1>Mouse events on a canvas using typescript</h1>
</header>
<aside>
<h2>
Instructions
</h2>
<p>
Click on the canvas to draw a line from the last point to the point where you have clicked The first point is at the canvas center.
</p>
<p>
note: the canvas is cleared on resize.
</p>
</aside>
<canvas id="canvas">This is the canvas</canvas>
<script src=canvas.js></script>
</body>
</html>
And this is the CSS
h1{
margin: 10px;
}
h2{
margin: 0px;
}
aside {
float: left;
border: 1px solid black;
padding: 10px;
margin: 20px;
width: 20%;
}
aside p{
font-family: Arial, Helvetica, sans-serif;
}
#canvas {
float: left;
background: lightgray;
border: 1px solid black;
margin: 20px;
width: 65%;
}
Handling the mouse event
Since we are using typescript, It is a good opportunity to make use of OOP. So let's create a class named canvasDraw that will handle all the operations related to drawing.
This class shall include an event handler that will receive the "mousedown" events
class CanvasDraw {
canvas: HTMLCanvasElement;
context: CanvasRenderingContext2D;
private x: number;
private y: number;
public constructor() {
this.canvas = <HTMLCanvasElement>document.getElementById("canvas");
this.context = this.canvas.getContext("2d");
this.canvas.addEventListener("mousedown", this.click, false);
}
public click = (event: MouseEvent): void => {
this.context.beginPath();
this.context.moveTo(this.x, this.y);
let rect = this.canvas.getBoundingClientRect();
this.x = (event.clientX - rect.left)/rect.width*this.canvas.width;
this.y = (event.clientY - rect.top)/rect.height*this.canvas.height;
this.context.lineTo(this.x, this.y);
this.context.stroke();
event.preventDefault();
}
}
let canvasDraw: CanvasDraw = new CanvasDraw();
Syntax to declare the event handler:
We assign a lambda function to an attribute of the class:
public click = (event: MouseEvent): void => { ... }
and we use this attribute to as a mousedown event listenr
this.canvas.addEventListener("mousedown", this.click, false);
This allows us to define the full code inside the class
this.canvas.addEventListener("mousedown", this.click, false);
This allows us to define the full code inside the class
Finding the clicked x/y position inside the canvas
The mouse event attributes clientX and ClientY cannot be used directly.
They need to be offset by the client top/left position and must be adapted by the canvas scaling.
let rect = this.canvas.getBoundingClientRect();
x = (event.clientX - rect.left)/rect.width*this.canvas.width;
y = (event.clientY - rect.top)/rect.height*this.canvas.height;
The canvas scaling problem
If you try the above program (don't forget to compile the typescript into JavaScript) You will see that the result is not as good as expected. This is because the HTML 5 canvas maintain a distinction between the canvas size (i.e. the image drawn) and the displayed size.By default the canvas size is 300px x 150 px but the displayed size is determined by the css. This means that most probably the displayed size will be much larger than the canvas drawing size leading to a "zoomed" image where all the pixels are visible and blurry.
To avoid this we should align the canvas size on the display size. But doing this is not straight forward.
Aligning canvas size on display size
To do this we use the following code
this.canvas.height = this.canvas.offsetHeight;
this.canvas.width = this.canvas.offsetWidth;
The resize event
There is no resize event on canvas in HTML 5 so we will have to use the event associated with the window object instead of the canvas objectclass CanvasDraw {
canvas: HTMLCanvasElement;
context: CanvasRenderingContext2D;
private x: number;
private y: number;
public constructor() {
this.canvas = <HTMLCanvasElement>document.getElementById("canvas");
this.context = this.canvas.getContext("2d");
this.canvas.addEventListener("mousedown", this.click, false);
//note resize event cannot be attached to the canvas
window.addEventListener("resize", this.resize, false);
this.resize(null);
}
public click = (event: MouseEvent): void => {
...
}
public resize = (event: UIEvent): void => {
this.canvas.height = this.canvas.offsetHeight;
this.canvas.width = this.canvas.offsetWidth;
}
}
Note the call to resize at the end of the constructor that ensure that the initial canvas drawing area size is correct at the application startup
Important note: on most browser the canvas is cleared after resize but this behavior is not guaranteed. To avoid compatibility problems we always clear the canvas with
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
The Complete program
Just for completeness and if you want to try it, this is the full listing of canvas.ts
class CanvasDraw {
canvas: HTMLCanvasElement;
context: CanvasRenderingContext2D;
private x: number;
private y: number;
public constructor() {
this.canvas = <HTMLCanvasElement>document.getElementById("canvas");
this.context = this.canvas.getContext("2d");
this.canvas.addEventListener("mousedown", this.click, false);
//note resize event cannot be attached to the canvas
window.addEventListener("resize", this.resize, false);
this.resize(null);
}
public click = (event: MouseEvent): void => {
this.context.beginPath();
this.context.moveTo(this.x, this.y);
let rect = this.canvas.getBoundingClientRect();
this.x = (event.clientX - rect.left);///rect.width*this.canvas.width;
this.y = (event.clientY - rect.top);///rect.height*this.canvas.height;
this.context.lineTo(this.x, this.y);
this.context.stroke();
event.preventDefault();
}
public resize = (event: UIEvent): void => {
//the problem is that we must align canvas resolution on CSS set display size
// otherwise there will be a zoom at display that will blur the lines
this.canvas.height = this.canvas.offsetHeight;
this.canvas.width = this.canvas.offsetWidth;
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.x = this.canvas.width / 2;
this.y = this.canvas.height / 2;
}
}
let canvasDraw: CanvasDraw = new CanvasDraw();