I've written this bit of JavaScript to just learn basics of drawing on/interacting with the HTML5 canvas element.
Just want to make sure I'm doing things "correctly" in case I have any glaring code gaffs or there's a more efficient way.
function start() {
canvas = document.getElementById("area");
// initiate ball
ball.init({
context: canvas.getContext('2d'),
color: "#F33",
radius: 30,
});
}
var ball = (function() {
var ball;
var mouseMoveEvent;
var prevMouseMoveEvent;
// set default options
var default_options = {
context: "", // required
radius: 20,
color: "#F33",
startX: window.innerWidth / 2,
startY: window.innerHeight / 2
};
return {
draw: function() {
// prep canvas
ball.o.context.canvas.width = window.innerWidth;
ball.o.context.canvas.height = window.innerHeight;
//capture current position values
var cur_x = ball.posX;
var cur_y = ball.posY;
//capture current context dimensions
var ctx_width = ball.o.context.canvas.width;
var ctx_height = ball.o.context.canvas.height;
if (ball.isGrabbed) {
//-------------- track ball with mouse when grabbed
mouseOffsetX = mouseMoveEvent.x - prevMouseMoveEvent.x;
mouseOffsetY = mouseMoveEvent.y - prevMouseMoveEvent.y;
ball.posX += mouseOffsetX;
ball.posY += mouseOffsetY;
// save previous mouse move state
prevMouseMoveEvent = mouseMoveEvent;
} else {
//-------------- bounding
var x_reach = Math.abs(ball.iterX) + ball.o.radius;
var y_reach = Math.abs(ball.iterY) + ball.o.radius;
if ((cur_x + x_reach) > ctx_width || (cur_x - x_reach) < 0)
ball.iterX = -(.70 * ball.iterX);
if ((cur_y + y_reach) > ctx_height || (cur_y - y_reach) < 0)
ball.iterY = -(.70 * ball.iterY);
ball.iterX *= .999;
ball.iterY *= .999;
ball.posX += ball.iterX;
ball.posY += ball.iterY;
}
//-------------- protect browser borders
// North
if (ball.posY - ball.o.radius < 0)
ball.posY = ball.o.radius;
// South
else if (ball.posY + ball.o.radius > ctx_height)
ball.posY = ctx_height - ball.o.radius;
// East
else if (ball.posX + ball.o.radius > ctx_width)
ball.posX = ctx_width - ball.o.radius;
// West
else if (ball.posX - ball.o.radius < 0)
ball.posX = ball.o.radius;
//-------------- draw
ball.o.context.beginPath();
ball.o.context.fillStyle = ball.o.color;
ball.o.context.arc(ball.posX, ball.posY, ball.o.radius, 0, Math.PI * 2, true);
ball.o.context.closePath();
ball.o.context.fill();
},
mouseDown: function(e) {
// grab ball
if (ball.o.context.isPointInPath(e.x, e.y)) {
prevMouseMoveEvent = e;
ball.isGrabbed = true;
}
},
mouseUp: function(e) {
// release
if (ball.isGrabbed) {
// set iter speed based on mouse speed on release
ball.iterX = mouseMoveEvent.x - prevMouseMoveEvent.x;
ball.iterY = mouseMoveEvent.y - prevMouseMoveEvent.y;
ball.isGrabbed = false;
}
},
mouseMove: function(e) {
if (ball.o.context.isPointInPath(e.x, e.y)) {
document.body.style.cursor = "move";
} else {
document.body.style.cursor = "default";
}
mouseMoveEvent = e;
},
init: function(options) {
ball = this;
//load up defaults
ball.o = default_options;
// merge in user options that exist
for (var attr in options) {
ball.o[attr] = options[attr];
};
// set starting values
ball.posX = ball.o.startX;
ball.posY = ball.o.startY;
ball.iterX = 0;
ball.iterY = 0;
ball.isGrabbed = false;
// attach events
window.onmousedown = ball.mouseDown;
window.onmouseup = ball.mouseUp;
window.onmousemove = ball.mouseMove;
// start
setInterval(ball.draw, 1);
},
};
})();
body {
margin: 0px;
}
#area {
width: 100%;
height: 100%;
margin: 0px;
background: #FF7
}
<body onLoad="start();">
<canvas id="area"></canvas>
</body>
1 Answer 1
This looks pretty good. In your start
function, the canvas
variable should be declared with a var; implicit globals are one of the Worst Parts.
function start()
{
var canvas = document.getElementById("area");
// ...
}
Second, the var statement can declare more than one variable at a time, so lines like
var cur_x = ball.posX;
var cur_y = ball.posY;
should be
var cur_x = ball.posX, cur_y = ball.posY;
Third, this line
for (var attr in options) { ball.o[attr] = options[attr]; };
- has an extra semicolon at the end and more importantly
- doesn't do a hasOwnProperty check.
The vast majority of the time, when you are iterating over object properties, you should check to make sure the properties actually exist on the object instead of the prototype chain. More more information read this article by Douglas Crockford. So it should look like:
for (var attr in options) {
if (options.hasOwnProperty(attr))
ball.o[attr] = options[attr];
}