I am trying to learn how to write good, clean, and efficient JavaScript. I consider myself a novice, at best, in JavaScript. Any comments are appreciated.
In an effort to become a better JavaScript programmer, I am trying to write a simple JavaScript animation using HTML5's new canvas element. Functionally speaking, when the canvas is clicked, a new circle (or Ball) is added to the canvas and begins moving to the lower right corner of the canvas. When the ball hits a wall or collides with another ball, the speed at which the x and y coordinates are moving is inverted.
Is it a good idea to be modifying the this
object? For instance does the following pattern make sense?
function Point(I) {
this.x = I.x;
this.y = I.y;
this.z = I.z;
return this;
}
Is there any reason why I shouldn't be sending a object and pulling variables off of the input object? I like the syntax for creation, but are there any problems with this pattern?
new Point({
x: 10,
y: 30
});
new Point({
x: 50,
y: 25
z: 3
});
Full Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Ball Collision</title>
<script src="js/modernizr-1.7.js"></script>
<script>
window.onload = function () {
//"use strict";
// Source: http://www.html5rocks.com/tutorials/canvas/notearsgame/
Number.prototype.clamp = function(min, max) {
return Math.min(Math.max(this, min), max);
};
if (Modernizr.canvas) {
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d');
canvas.width = 500;
canvas.height = 375;
canvas.style.border = "3px solid red";
function Point(I) {
console.log("Point created");
this.x = I.x;
this.y = I.y;
this.z = I.z;
return this;
}
function Ball(I) {
console.log("Ball created");
var SPEED_LIMIT = 10;
this.point = new Point(I.point);
this.radius = (I.radius > 10 ? I.radius : 10);
this.color = I.color;
this.speed = new Point(I.speed);
this.collision = false;
this.mass = I.mass || 1;
this.draw = function() {
this.point.x = this.point.x.clamp(0 + this.radius, canvas.width - this.radius);
this.point.y = this.point.y.clamp(0 + this.radius, canvas.height - this.radius);
ctx.beginPath();
ctx.arc(this.point.x, this.point.y, this.radius, 0, Math.PI*2, false);
ctx.closePath();
ctx.fillStyle = this.color;
ctx.fill();
};
this.update = function() {
this.clear();
if ( this.point.x <= this.radius || this.point.x >= (canvas.width - this.radius)) {
this.speed.x *= -1;
}
if ( this.point.y <= this.radius || this.point.y >= (canvas.height - this.radius)) {
this.speed.y *= -1;
}
if ( this.speed.x > SPEED_LIMIT ) {
this.speed.x = SPEED_LIMIT;
}
if ( this.speed.x < SPEED_LIMIT * -1 ) {
this.speed.x = SPEED_LIMIT * -1;
}
if ( this.speed.y > SPEED_LIMIT ) {
this.speed.y = SPEED_LIMIT;
}
if ( this.speed.y < SPEED_LIMIT * -1 ) {
this.speed.y = SPEED_LIMIT * -1;
}
this.point.x += this.speed.x;
this.point.y += this.speed.y;
};
this.clear = function() {
ctx.beginPath();
ctx.arc(this.point.x - 1, this.point.y - 1, this.radius + 2, 0, Math.PI*2, false);
ctx.closePath();
ctx.fillStyle = "#fff";
ctx.fill();
};
return this;
}
var balls = [];
function handleCollisions() {
function collide(ball1, ball2) {
ball1.speed.x *= -1;
ball2.speed.x *= -1;
ball1.speed.y *= -1;
ball2.speed.y *= -1;
}
function checkCollisions() {
var i, j, a, b;
for ( i = 0; i < balls.length; i++ ) {
for ( j = i+1; j < balls.length; j++ ) {
a = balls[i];
b = balls[j];
var dx = Math.abs(a.point.x - b.point.x);
var dy = Math.abs(a.point.y - b.point.y);
var d = (dx*dx)+(dy*dy);
var r = (a.radius + b.radius) * (a.radius + b.radius);
if ( d < r && ( !a.collision || !b.collision ) ) {
a.collision = true;
b.collision = true;
collide(a, b);
} else if ( d > r ) {
a.collision = false;
b.collision = false;
}
}
}
}
checkCollisions();
}
function update() {
balls.forEach(function(ball) {
ball.update();
});
handleCollisions();
}
function draw() {
balls.forEach(function(ball) {
ball.draw();
});
}
var FPS = 30;
var loop = setInterval( function() {
update();
draw();
}, 1000/FPS);
canvas.onclick = function(e) {
var radiusAndMass = Math.floor(Math.random() * 50) + 1;
var speed = Math.floor(Math.random() * 10) + 1;
balls.push(new Ball({
point: {
//x: Math.floor(Math.random() * canvas.width),
//y: Math.floor(Math.random() * canvas.height),
x: e.offsetX,
y: e.offsetY
},
color: "#"+((1<<24)*Math.random()|0).toString(16),
radius: radiusAndMass,
mass: radiusAndMass,
speed: {
x: speed,
y: speed
}
}));
};
document.getElementById('canvas-container').appendChild(canvas);
}
};
</script>
</head>
<body>
<div id="wrap">
<div id="canvas-container">
</div>
</div>
</body>
</html>