[Update: This post was written in my early days of JavaScript. Although the main ideas are good, my use of separate JavaScript files for holding object prototype definitions is probably misguided. I was following an ActionScript paradigm too closely. The JavaScript should probably all be in one place together.]
Welcome to the next installment in a series of simple HTML5 Canvas tutorials. In our previous posts, we presented a simple example of dragging objects with the mouse, and then later extended this example with some more advanced features:
- Part 1: Dragging objects – a very simple HTML5 Canvas example
- Part 2: Smooth dragging – extending our simple dragging example
Although the new example in this post looks very similar on the surface display objects which can be dragged with the mouse behind the scenes in the code there are some significant differences. Here we will use our dragging example to illustrate object-oriented programming in JavaScript. We will use classes to represent the display objects. These classes are JavaScript Objects which include methods both for drawing the particles and for determining whether a given point hits the display object.
Click here or on the screenshot below to see the demo.
Download
Download the complete, well-commented source code here: OOPDragging.zip
About the Code
The basic ideas behind detecting mouse events over display objects, and creating smooth motion were covered in the earlier tutorials referenced above. Here we are using this simple dragging example as a context for discussing object-oriented programming in JavaScript.
The display object classes
If you are coming from a different coding experience, then the construction of a JavaScript class will look a lot different from, say, an ActionScript class. But the idea is the same: the class defines an object which retains information about its properties, along with methods for performing important tasks related to that object. In our example here we define two separate display object classes: a SimpleDiskParticle
representing a filled-in circle and a SimpleSquareParticle
representing a filled-in square. These are not the most exciting display objects, but the simplicity will allow us to concentrate on the coding method.
When we create a new “particle” using one of these classes, we will be creating a JavaScript Object which retains information about the position, size, and color of the particle. The SimpleDiskParticle
and the SimpleSquareParticle
both include a drawToContext()
method for drawing the particle to a canvas context, as well as a hitTest()
method that we will use to see if a mousedown
event occurs over the particle.
These classes are very simple. Let’s take a look at the complete code for the SimpleDiskParticle
class, which is defined in the separate SimpleDiskParticle.js file. The class could also have been created within the main application; the external file is simply used for organizational purposes (and it also mimics the way classes are defined in ActionScript). The SimpleDiskParticle
code begins with a constructor:
function SimpleDiskParticle(posX, posY) {
this.x = posX;
this.y = posY;
this.color = "#FF0000";
this.radius = 10;
}
Note the use of the keyword this
which refers to the current object being defined by the constructor function. The consturctor simply defines an object and attaches to it some needed properties. Later when we want to create a new SimpleDiskParticle
at the position with coordinates (tempX, tempY)
, we will use the new
keyword:
tempShape = new SimpleDiskParticle(tempX, tempY);
Note that the constructor takes as an argument the desired coordinates, and it also sets a default color and size for the particle. The color and size can be changed later if we like by explicitly setting these properties to new values. We will show how these classes are used later.
We now build the functions which will handle drawing the particle and performing a hit test. These functions must be attached to a prototype object. Each newly created SimpleDiskParticle
will inherit these functions from the prototype object. This is essentially the same thing as defining an instance method for an ActionScript (or Java) class. The drawing and hit test functions are defined as follows:
SimpleDiskParticle.prototype.drawToContext = function(theContext) {
theContext.fillStyle = this.color;
theContext.beginPath();
theContext.arc(this.x, this.y, this.radius, 0, 2*Math.PI, false);
theContext.closePath();
theContext.fill();
}
SimpleDiskParticle.prototype.hitTest = function(hitX,hitY) {
var dx = this.x - hitX;
var dy = this.y - hitY;
return(dx*dx + dy*dy < this.radius*this.radius);
}
Note that the hit test is based on the circular geometry of the particle, as explained in our first post in this series.
Of course, a square particle has to be drawn differently, and its geometry requires a hit test to be done differently. So by associating the drawing and hit test functions with the individual particles, we can simply call these instance methods in our main code and the methods will do the right thing depending on what type of object we are dealing with. We will explain this in more detail below.
Using the classes in the main code
The basic set up for this example is the same as the two previous examples in this series. What has changed is how we create new display objects to add to our array shapes
, and how we draw these shapes and perform hit tests with the instance methods described above. We already explained above how the new
keyword is used to create new instances of our particle classes. Let's take a look at it in context. Within the makeShapes
function, and inside a for-next loop iterated to create several objects, we create a random color tempColor
, including a randomized alpha (transparency) value, and select a random point with coordinates (tempX, tempY)
. Then we randomly create either a disk or square particle and initialize its position at the point (tempX, tempY)
:
if (Math.random() < 0.5) {
tempShape = new SimpleDiskParticle(tempX, tempY);
}
else {
tempShape = new SimpleSquareParticle(tempX, tempY);
}
After this is done, we explictly set the size and color of the newly created particle, changing these values from the default values which were set inside the constructor:
tempShape.color = tempColor;
tempShape.radius = tempRad;
Finally, we add the new shape to our array:
shapes.push(tempShape);
The array shapes
now contains a list of our particles. Some of these particles are disk particles, some are squares. Later, when we want to test whether a mouse position (mouseX, mouseY)
hits the particle shapes[i]
in our array, we simply check the Boolean value returned by the hitTest function associated with the particle: shapes[i].hitTest(mouseX, mouseY)
. The beauty of the object-oriented approach is that the function hitTest
will do the right thing depending on what kind of shape shapes[i]
is.
Similarly, when we draw the particles inside the drawShapes()
function, we simply need to call the drawing method associated to the particle class:
for (i=0; i < numShapes; i++) {
shapes[i].drawToContext(context);
}
Note that we have to pass to this function the canvas context to which we would like to draw the particles.
To see this all put together, download the source code.
can you attach dragging events to only to specific circles…lets say only to 3 circles out of 7 circles ?
June 22, 2013 @ 6:56 am
|Hi sanjog,
Sorry it took me so long to reply!
Sure, you can make only some of the drawing objects draggable. There are many ways to do this. If the immovable circles are going to be behind all the others, then they really are just part of a background that doesn’t change, and you can redraw this background on every update.
July 9, 2013 @ 10:20 pm
|Great tutorial and great explanation dan… wish could see a tutorial for creating 2 or more objects and their interaction between each other…that would be a clarifying for OOP in javascript…
keep doing such a great job!!!
September 9, 2013 @ 5:50 am
|Thanks, Luis!
Actually, although this example has some good ideas in it, I have since learned more about JavaScript and I think some parts of this approach don’t quite fit the JavaScript model. (The post about keeping files separate is a bad idea.) I should rewrite this sometime!
September 23, 2013 @ 11:30 am
|Hey thanks, great article!… I’m just wondering if I can do something like this, but images instead of circles… is this possible? I can’t figured out
March 28, 2014 @ 5:16 pm
|Yes, you definitely can do this with images. You would essentially be using the square objects (but you could change them to rectangles), and use drawImage() to draw the image instead of drawing a rectangle with drawRect. Here is an example of copying images to a canvas.
March 30, 2014 @ 4:52 pm
|Hi Dan
Thank you so much, just what I needed, and a great tutorial.
My next step is being able to attach a text box to the objects, that would move and rotate/scale in sync.
Any hints?
May 15, 2014 @ 8:14 pm
|You can draw text to a canvas with the fillText() method. The draggable objects could have a
text
parameter that you set to the string you like, then you could incorporate this string into thedrawToContext
function.May 15, 2014 @ 10:20 pm
|How can I drag one object without deleting the whole canvas ?
July 1, 2014 @ 12:21 pm
|If you mean how can you drag one object without erasing and redrawing everything on the canvas, this can be done but it requires a lot more work. You need to keep track of what part of the canvas needs to be redrawn, and only erase and redraw this area. But that’s easier said than done.
This was a simple example, but there are some good libraries for dealing with individual display objects if you want to do this sort of thing a lot. For example, have a look at EaselJS.
July 3, 2014 @ 3:00 am
|Great Tutorial… I am just wondering if I can save the new state of circles after dragging so that once user revisit page he/she gets circle where he/she left after dragging…
any Idea…
October 3, 2014 @ 11:32 pm
|I’m sure this could be done with cookies, but I’m afraid I don’t know how to work with cookies. You could look it up.
December 20, 2014 @ 3:24 pm
|Great tutorial… I am just wondering if we can save state of circles after dragging so that whenever user returns he/she gets the same view where he/she left after dragging of circles.
Any idea how to get it
October 3, 2014 @ 11:54 pm
|I’m afraid I don’t know how to do that. I’m sure it’s possible…but not my area of expertise!
October 6, 2014 @ 2:58 am
|Is HTML5 considered Object Oriented Programming (OOP); I thought it was less structured than other programming languages.
October 16, 2014 @ 7:15 pm
|Sorry I missed this comment you posted a while ago! You are right that JavaScript is not inherently an object-oriented programming language. I wrote this blog post before I fully appreciated that. Still, there are a lot of aspects to JavaScript that make it similar to OOP, and one can mimic classic OOP using JavaScript. The main ideas here are probably still good, but there are certainly other ways to accomplish this same effect with a different organization of code.
December 7, 2014 @ 12:29 pm
|Hai
Is it possible to detect which i am dragging
March 4, 2016 @ 9:42 am
|You can certainly attach to each object a number which signifies which object it is.
March 25, 2016 @ 10:03 pm
|Hi dan,
Is it possible to draw triangles, stars etc.. and add to shapes array
July 30, 2016 @ 2:25 pm
|Sure! Each shape needs its own hitTest function which describes when a given point is within its boundaries.
October 4, 2016 @ 4:23 am
|Hi dan,
Is it possible to see coordinate of each shape and when we move this then coordinates also change of particular shape.
January 25, 2017 @ 7:58 am
|Hi Vijay, Sorry for the long delay in my response. Yes, you can get the coordinates of the objects, and use them for whatever you want to do. In fact, each JavaScript object representing a display object has x and y coordinates that are attributes for the object. For example, in lines 176 and 177, the coordinates are changed to make the object move.
January 30, 2017 @ 4:49 am
|Is it possible to calculate the percentage 2 objects overlap?
January 26, 2017 @ 2:18 am
|Sure, but it requires a bit of math based on the center points and shapes. For example, if the two objects are circles, an algebraic expression involving the two center point coordinates and the two radii would work this out. I don’t have the time to work it out, though!
January 30, 2017 @ 4:53 am
|