Using Objects
What we’ve done with functions so far is nice and all, but for the purposes of this example, it doesn’t add a whole lot of value. We could have just stashed the contents of drawCircle
into the for
loop itself, and everything would have been just fine:
for (var i = 0; i < 40; i++) {var r = Math.round(15 + Math.random() * 150);var xPos = Math.round(Math.random() * myCanvas.width);var yPos = Math.round(Math.random() * myCanvas.height);context.beginPath();context.arc(xPos, yPos, r, 0, 2 * Math.PI, true);context.fillStyle = "rgba(41, 170, 255, .1)";context.fill();}
While I totally get the advantage functions bring to the table when it comes to code clarity and reuse, let’s go further.
For the ultimate level of control that leaves the functions-only approach in the dust, we can use objects. As you probably noticed by now, once things are drawn to the canvas
, there really isn’t much you can do. You can’t access what you’ve just drawn as a single entity. It doesn’t matter if you are calling draw commands directly or relying on a custom function like we saw in the previous section. Once the pixels get placed on the canvas
, they lose any individuality and become just another brick in the wall. This is one of the things that makes drawing on the canvas
more difficult compared to DOM elements.
We can’t fundamentally alter how the canvas
works. What we can do is come up with ways to track and manipulate the things we draw on the canvas
using good old JavaScript Objects. If you aren’t familiar with JavaScript objects and creating them using Object.create
, I highly recommend you take a few moments to brush up on them. The Introduction to Objects and the Deeper Look at Objects tutorials should help you out.
What sets our Object approach apart is that each circle we draw will be associated with a corresponding object. This object looks as follows:
// the circle object!!!var circle = {idValue: -1,radius: 0,xPos: 0,yPos: 0,color: "rgba(41, 170, 255, .1)",setup: function (x, y) {this.xPos = x;this.yPos = y;this.radius = Math.round(15 + Math.random() * 150);},setColor: function (newColor) {this.color = newColor;},draw: function () {context.beginPath();context.arc(this.xPos, this.yPos, this.radius, 0, 2 * Math.PI, true);context.fillStyle = this.color;context.fill();}};
Notice that we have a few properties that help determine our shape’s size, position, and color. We then have a few functions that are responsible for setting up each of our circles and drawing them to the screen.
To use this object as part of our example, all we need to do is create new objects based on circle
, initialize it using the custom setup
method, and then call draw
to get the circle to appear on screen. Using our for
loop approach that we saw earlier, this will look as follows:
function drawAllCircles() {for (var i = 0; i < 40; i++) {var r = Math.round(15 + Math.random() * 150);var xPos = Math.round(Math.random() * myCanvas.width);var yPos = Math.round(Math.random() * myCanvas.height);var newCircle = Object.create(circle);newCircle.setup(xPos, yPos);newCircle.idValue = i;newCircle.draw();}}drawAllCircles();
If you add this code along with the circle object that we saw a few moments ago, your canvas will display the semi-transparent blue circles that we had originally started off with. Now, I mentioned that this approach makes it easier to track each of the circles we’ve drawn. Right now, we aren’t taking advantage of that capability.
The easiest way to track each circle we’ve drawn is to store its corresponding circle
object inside an array:
var circles = [];function drawAllCircles() {for (var i = 0; i < 40; i++) {var xPos = Math.round(Math.random() * myCanvas.width);var yPos = Math.round(Math.random() * myCanvas.height);var newCircle = Object.create(circle);newCircle.setup(xPos, yPos);newCircle.idValue = i;newCircle.draw();circles.push(newCircle);}}drawAllCircles();
Our circles
array now contains data about every single circle that we’ve drawn. If you want to change the color of a particular circle, you can just retrieve it from the circles
array and use the setColor
and draw
methods to update that circle.
For example, if we wanted to set the color of the first circle we drew to a yellow color with no transparency, we could do something like this:
var firstCircle = circles[0];firstCircle.setColor("rgba(255, 204, 0, 1)");firstCircle.draw();
Here is how this works. When the draw
method is called, we re-draw our first circle from the circles
array with the same position and radius values it had initially. The only thing that has changed is the color…which we updated by calling the setColor
method that updated our circle
object’s public color
property.
Now, there is one other thing to note. When we re-draw our circle, it gets drawn over everything else. That’s just how the canvas
works, and maintaining the exact draw order is tricky. If we had to try to maintain that, we would need to redraw all of the overlapping circles in the exact order they originally were drawn in. That gets really complicated really quickly, and unless you really need that optimization, it would probably be easier to just redraw all of the circles at once at that point.