.. post:: 2016-04-16 :category: Programming :tags: JavaScript, Canvas :excerpt: 2 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 element`_ code. Move the mouse over the square below and try to catch the blue dot with the red one. .. raw:: html Sorry, this needs a browser with <canvas> support. .. contents:: Content :depth: 2 :local: HTML ---- The HTML for this is quite simple. Just the ```` 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/```` support. .. sourcecode:: html Sorry, this needs a browser with <canvas> support. 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 :math:`[a,b)`. .. sourcecode:: javascript '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)'``). .. sourcecode:: javascript 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. .. sourcecode:: javascript 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… .. math:: a^2 + b^2 = c^2 …we derive a function to calculate the distance (`d`) between the centers of two balls with the cartesian coordinates `(x₁,y₁)` and `(x₂,y₂)`: .. math:: d = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2} Looks a bit less elegant with all the `Math` function calls compared to the mathematical formular layout: .. sourcecode:: javascript 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: .. math:: d < r_1 + r_2 Or in code: .. sourcecode:: javascript Ball.prototype.collidesWith = function(other) { return this.distanceTo(other) < this.radius + other.radius; }; The Field ^^^^^^^^^ Given an HTML5 ```` 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()`. .. sourcecode:: javascript 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. .. sourcecode:: javascript 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 ^^^^^^^^^^^^^^^ .. sourcecode:: javascript 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. .. sourcecode:: javascript 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. .. sourcecode:: javascript 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. .. sourcecode:: javascript window.onload = function () { var field = new Field(document.getElementById('dot-chase'), BALL_RADIUS); field.update(); }; .. _HTML5 element: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API .. _Python: http://python.org/