Doodle-js
Gentle reader, this page demonstrates a depreciated version of Doodle.js. Check the landing page or GitHub page for up-to-date examples, information, and code.
I’ve been having a lot of fun with the HTML Canvas element and really look forward to utilizing the next-gen Javascript engines the browsers are starting to roll out.
However the drawing commands for it are a bit… basic. It can be difficult to work creatively when you have to constantly handle the “low level” details — that’s why I wanted to create a library that did it for me. Doodle.js attempts to create a fun and easy way to interact with the Canvas that is lightweight, flexible, and functional. While it contains a few shape primitives it is not meant to be a full-fledged drawing api, rather a framework that allows you to build sprites and interact with them in an expressive way.
This is a 0.1 development release so please treat it accordingly. Bugs are sure to crop up so it’s best to test things out with your browser’s debug console open. I’ve been trying it on Chrome, Firefox 3.0.8, and IE 8 (using excanvas).
- GitHub Project
- doodle-0.1.1.js (41k)
- doodle-0.1.1.min.js (21k)
Here are a few demos I put together that you might want to take a look at before reading on.
- Spiral Pattern (drawing multiple times to canvas)
- Some Bouncy Balls (a little physics)
- Girl Comin’ to Get Ya (using images — no IE, sorry)
- Hearts a Flutter (custom shapes)
- Marbles in Space (simulating 3D)
Demo source available here.
This is the setup for a basic HTML file that we’ll use for the rest of the examples. You’ll see that it includes a link to excanvas.js (for IE) and the Doodle library. In the body of the document we have a canvas element with an id of “my_canvas”. If your browser doesn’t support canvas then some fall back content will appear instead.
With the basics in place we can add some Javascript to draw a square.
To avoid cluttering up the namespace, when you import the Doodle library it will create a single global object named $doodle . Everything in the library can be accessed through this variable, but to save some keystrokes we’re going wrap it up in anonymous function call using a popular Javascript idiom.. The “oo” variable was picked because it’s easy to type, easy to remember (d-oo-dle), and easy for our eyes to parse when we look over the code (it only takes a very cursory knowledge of male psychology to understand that).
Now that we can access the library, we’ll first feed it the id of our html canvas. We then create a rectangle with oo.rect() by passing it a Javascript object that contains our named parameters. We want a square 50 pixels wide that is situated at the coordinates (25, 25), which starts from the top-left point of our canvas element. Finally we render the rectangle to the canvas element by making a call to .draw().
Pretty easy eh? Well let’s say we want to draw our little box again, this time starting exactly in the middle of our first box. Let’s take a look:
So what did we do here? You’ll notice that we made a call to .draw() twice, but unlike our first example where .draw() ends with a semicolon; we omit that and instead make a call to the objects .modify() method. We can do this because the methods of a doodle object return the same object that called it. This allows us to chain functions together creating complex behavior in relatively few lines of code.
Now how do you reference a value from an object that hasn’t been created yet? (Well the object has been created, it just hasn’t been assigned to a variable yet.) Once inside the .modify() method we demonstrated two ways of referencing our previously declared values. With the x property we pass it a string (surrounded by quotes) and start it with the relative assignment operator “+=”. This adds 25 to what was previously stored in x (25), so now x holds the value of 50. It’s important to know that Doodle knows quite a few of these relative assignment operators, but you can’t simply pass any code here. This code is not being evaluated in a Javascript sense (that’d be dangerous), instead it’s just looking for the operator. Also note that it does not support passing variables here.
When we modify the y property we are passing it an anonymous function that is called with itself as the arguments and the return value is assigned back to y. This is a very flexible and powerful way to in-line functions and return the results of any equation. The function is called from within the object, passing itself as the first argument, in this case assigned to “o”. The second argument is the property you are modifying, in this example “p”. You don’t have to use these but they are there for convenience. Finally we change the fill color to blue and then make a call to .draw() a second time. So we’ve created a box, drawn it, moved it, then drawn it again. Keep in mind that this is the same box, not a separate object, so we can’t change the order they appear on screen after they have been drawn. For that you’d need to create 2 separate objects and assign them to a new group: oo.group().add(box1, box2).draw(). The order they are drawn depends on the value of each object’s “z” property.
Transforms
All transformations to an object are represented as a 2d matrix and stored in it’s obj.matrix property. If your interested about how they work I will point you to a nice tutorial on the subject. Doodle objects support a number of methods for transforming it’s matrix: translate, rotate, scale, transform — or you may operate on the matrix itself.
In this example we have created a box and drawn it 5 times, changing it’s color each time to better illustrate the effects of the applied transforms. Our box is first drawn at an initial position of (25, 25) and given the color red (in hexadecimal notation).
We then change it’s color to green (this time in rgb notation), move it right 50 pixels on the x axis and draw it again. Next, we change to purple and rotate 45 degrees before drawing again. Notice it rotates around the default axis point which is it’s top-left point, or in local space at (0,0). [This would correspond to it’s position in global space on the canvas which is now (75, 25).]
Next we perform 2 transformations by chaining them together. First we move the box 50 pixels along it’s now rotated x axis. We then multiply it’s scale by 1.5 along it’s x and y (the single parameter we passed here is short for .scale(1.5, 1.5) ) and draw the now yellow box. Finally, we change it’s color to blue, and do the same thing as we did in the previous line — translating 50 pixels on x and increasing it’s scale by 1.5, the difference being that we are now applying the transform at the same time by using matrix notation.
Loops
To loop over an object you must give it some parameters to change — otherwise it will continue to write the same thing over and over again, looking like a static image. In this example we will draw a circle at the center of our canvas, then draw it 24 more times rotating around this point — each time adjusting it’s position and assigning it a random color. We’ll do this with a traditional “for-loop” to start and then look at some built-in methods for accomplishing the same task.
Now we’re going to draw the exact same thing except we’ll utilize the object’s built-in .loop() method. It’s up to you which way you feel comfortable with, but this is a more functional way of doing things if you’re into that sort of thing. It also demonstrates some of the flexibility you have when assigning values to object properties in Doodle. You’ll notice that the first dot.draw() call has been omitted in this example. The reason is a .loop() takes a modification parameter that will be assigned on subsequent iterations. Meaning the first call to loop will always draw the object in it’s current state, additional loops will apply the modifications. There is also a special property in the loop parameter object, .on_update. This is a function that will be run after each iteration of the loop allowing you to increment variables or alter properties. You could modify everything from within this function if you really wanted to, but that might be a bit confusing.
Another option for the .loop() method is to pass it a function instead of an object and to use .modify() within it.
Now let’s take a look at this loop:
This loop runs indefinitely and will completely clear the canvas every 800th frame. We’ve added a framerate parameter to the loop which dictates how fast it’s run; this can take a numeric value in the measure of milliseconds or as we can see here as a string with “fps” appended.
In this loop we are starting to get an idea of the animation capabilities inherent. By changing the count to zero and setting the clear every frame parameter to true we can put our little circle in motion. In fact all sprites have an .animate(params, framerate) method that is just a .loop() that runs forever and clears every frame. Animation is very experimental at this point, especially when working with multiple objects since clearing the canvas will have conflicts. A safer way is to use the global animate function in Doodle:
Images
You can import images and draw them as normal shapes. Though since they take time to load it can be a little tricky sometimes having them interact with the rest of your program. You can pass a function to it’s on_load parameter that will be called when it’s ready to use. The below example worked fine for me in Chrome and Firefox, but unfortunately I’ve had difficulties in IE applying matrix transforms to images.
Custom Shapes
Doodle is not meant to be a full-fledged drawing library, rather a way to better group and organize your own shapes. To that end it’s easy to create custom objects and assign them shapes using the canvas drawing api. We’ll use the $doodle.gfx object to access these commands and assign them to the shape function of an extended sprite object.
Groups
Objects can be grouped together and transformed around a common axis. The order an object is drawn in a group is determined by it’s z property. A higher number means it will appear “on top” of another object. You can change the way a group composes it’s objects by assigning a composite operation to the group.composite property. A list of these can be found here. Groups haven’t been fully implemented yet, so your mileage will vary.
Interaction
To get the mouse coordinates I find it easiest to use jQuery, but you could use any of the great Javascript libraries out there.
Conclusion
Like I said before, this is the first development release so I’m still hunting bugs. If you find something and can help out, please shoot me a message. I’ll get some more extensive docs once the API settles down, until then just punch around the source code.