Chasing a dot on a HTML5 canvas¶
A forum question about how to detect a collision of two balls for a school project (in Python) lead me to write my first HTML5 <canvas> element code.
Move the mouse over the square below and try to catch the blue dot with the red one.
HTML¶
The HTML for this is quite simple. Just the <canvas>
element with an id
to get the element in JavaScript, the width and height as attributes, and
some content that shows in browsers without HTML5/<canvas>
support.
<canvas id="dot-chase" width="400" height="400">
Sorry, this needs a browser with <canvas> support.
</canvas>
JavaScript¶
The programmatic part starts with the definition of the radius of the balls and
the function randomInt() to get random integer numbers from the given interval
.
'use strict';
var BALL_RADIUS = 10;
var randomInt = function (a, b) {
return Math.floor(Math.random() * (b - a) + a);
};
A Ball¶
Ball instances have a center coordinate (x,y), a radius, and a color
that can be any CSS color value, like names ('green'
) or an rgb()/rgba()
notation ('rgb(0, 1, 0)'
).
var Ball = function(x, y, radius, color) {
this.x = x;
this.y = y;
this.radius = radius;
this.color = color;
};
Drawing a Ball¶
That’s quite long and looks a bit complicated, mostly because there is no simple
call to a circle()
function and properties like color and shadow offset,
blur, and color have to be set on the context object.
Ball.prototype.draw = function(context) {
context.save();
try {
context.fillStyle = this.color;
context.shadowOffsetX = 3;
context.shadowOffsetY = 3;
context.shadowBlur = 2;
context.shadowColor = 'rgba(0, 0, 0, 0.5)';
context.beginPath();
context.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
context.fill();
} finally {
context.restore();
}
};
Distance between two Balls¶
Taking the good old Pythagorean theorem…
…we derive a function to calculate the distance (d) between the centers of two balls with the cartesian coordinates (x₁,y₁) and (x₂,y₂):
Looks a bit less elegant with all the Math function calls compared to the mathematical formular layout:
Ball.prototype.distanceTo = function(other) {
return Math.sqrt(
Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2)
);
};
Do they collide?¶
The balls collide iff the distance d calculated by the function in the previous section is smaller then the sum of the radii of both balls:
Or in code:
Ball.prototype.collidesWith = function(other) {
return this.distanceTo(other) < this.radius + other.radius;
};
The Field¶
Given an HTML5 <canvas>
and the radius of the balls, Field instances
contain everything needed to handle user input and keep the ”game” going.
That’s the canvas to draw on, the width and height of the playing field, the
context used to draw on, and the two balls.
The player’s ball starts off canvas and will be placed where the mouse pointer is by the handler for mouse moves: Field.onMouseMove().
var Field = function (canvas, ballRadius) {
this.canvas = canvas;
this.ballRadius = ballRadius;
this.width = this.canvas.getAttribute('width');
this.height = this.canvas.getAttribute('height');
this.context = this.canvas.getContext('2d');
this.randomBall = null;
this.randomlyPlaceBall();
// Place outside visible area.
this.playerBall = new Ball(
-this.ballRadius, -this.ballRadius, this.ballRadius, 'red'
);
this.canvas.addEventListener('mousemove', this.onMouseMove.bind(this));
};
Randomly place the other ball¶
Place the other ball on a random position within the field by placing the center at least the radius of the ball away from the border of the canvas.
Field.prototype.randomlyPlaceBall = function() {
this.randomBall = new Ball(
randomInt(this.ballRadius, this.width - this.ballRadius),
randomInt(this.ballRadius, this.height - this.ballRadius),
this.ballRadius,
'blue'
);
};
Clear the field¶
Field.prototype.clear = function() {
this.context.clearRect(0, 0, this.width, this.height);
};
Update the field¶
Clears the field and then redraws both balls at their current position.
Field.prototype.update = function() {
this.clear();
this.randomBall.draw(this.context);
this.playerBall.draw(this.context);
};
On each mouse move¶
Moving the mouse over the canvas sets the player’s ball center to the mouse pointer position relative to the upper left corner of the canvas.
If the player’s ball collides with the other ball, the other ball gets placed at a new random position.
At the end of the handler function the display is updated.
Field.prototype.onMouseMove = function(event) {
var canvasRect = this.canvas.getBoundingClientRect();
this.playerBall.x = event.clientX - canvasRect.left;
this.playerBall.y = event.clientY - canvasRect.top;
if (this.playerBall.collidesWith(this.randomBall)) {
this.randomlyPlaceBall();
}
this.update();
};
Connecting HTML and JavaScript¶
Get the canvas from the document after the page is loaded, instanciate a Field object and call its update() method to draw the initial state of the game.
window.onload = function () {
var field = new Field(document.getElementById('dot-chase'), BALL_RADIUS);
field.update();
};