Before drawing anything in a browser, ask yourself three questions:
- Do you need to support older browsers?If the answer is yes, then your only choice is Raphaël. It handles browsers all the way back to IE 7 and Firefox 3. Raphaël even has some support for IE 6, although some of its underlying technology cannot be implemented there.
- Do you need to support Android?Android doesn’t support SVG, so you’ll have to use Paper.js or Processing.js. Some rumors say that Android 4 will handle SVG, but the majority of Android devices won’t support it for years.
- Is your drawing interactive?Raphaël and Paper.js focus on interaction with drawn elements through clicking, dragging and touch. Processing.js doesn’t support any object-level events, so responding to user gestures is very difficult. Processing.js can draw a cool animation on your home page, but the other tools are better for interactive applications.
Paper.js, Processing.js and Raphaël are the leading libraries for drawing on the Web right now. A couple of others are up and coming, and you can always use Flash, but these three work well with HTML5 and have the widest support among browser vendors.
Choosing the right framework will determine the success of your project. This article covers the advantages and disadvantages of each, and the information you need to make the best choice.
All of the code in this article is open source and can be run on the demo page that accompanies this article.
Overview
|
Paper.js |
Processing.js |
Raphaël |
Technology |
canvas tag |
canvas tag |
SVG |
Language |
PaperScript |
Processing script |
JavaScript |
Browsers |
IE 9 |
IE 9 |
IE 7 |
Mobile |
Yes |
Yes |
iOS only |
Model |
Vector and raster |
Raster |
Vector |
Size |
56 KB |
64 KB |
20 KB |
It’s all JavaScript once the page runs, but the frameworks take different paths to get there. Raphaël is written directly in JavaScript, but Paper.js uses PaperScript, and Processing.js uses its own script. They all support Firefox, Chrome and Safari, but Internet Explorer is an issue — Paper.js and Processing.js use the canvas
tag and thus require IE 9.
PaperScript is a JavaScript extension that makes it possible to write scripts that don’t pollute the global namespace. This cuts down on JavaScript conflicts. PaperScript also supports direct math on objects such as Point
and Size
: you can add two points together as if they were numbers.
Processing.js is based on a framework named Processing, which runs in the Java Virtual Machine. You define int
and float
instead of var
, and you can use classes with Java-style inheritance. While the Processing.js script looks a little like Java, it’s more like JavaScript and doesn’t require many of the more complex features of Java.
Using all three libraries is easy if you have some familiarity with JavaScript.
Getting Started
Start by importing each library. The process for setting each up is a little different.
Setting Up Paper.js
<head>
<script src="paper.js" type="text/javascript" charset="utf-8"></script>
<script type="text/paperscript" canvas="paperCircle" src="paper_circle.pjs" id="script"></script>
</head>
<body>
<canvas id="paperCircle" class="canvas" width="200" height="200" style="background-color: white;"></canvas>
Paper.js specifies a script type of text/paperscript
and the ID of the canvas
tag that you’ll draw on. It uses that ID to know where to draw.
Setting Up Processing.js
<head>
<script src="processing.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<canvas width="200" height="200" class="canvas" data-processing-sources="processing_circle.java"></canvas>
Processing.js uses the data-processing-sources
attribute of the canvas
tag to import your drawing. I use a .java
extension for Processing’s source file so that my editor color-codes it properly. Some authors use a .pde
or .pjs
extension. It’s up to you.
Setting Up Raphaël
<head>
<script src="raphael-min.js" type="text/javascript" charset="utf-8"></script>
<script src="raphael_circle.js" type="text/javascript" charset="utf-8"></script>
</head>
Raphaël is imported like any other JavaScript file. It works well with jQuery’s ready
function or any other JavaScript framework.
Now we can start drawing.
Object-Oriented Drawing
Both Paper.js and Raphaël use object-oriented drawing: you draw a circle and get back a circle object. Processing.js draws the circle and doesn’t give you anything back. The following simple example makes it clear. Let’s start with a circle in the middle of the screen at point 100,100
.
Paper.js:
var circle = new Path.Circle(new Point(100, 100), 10);
circle.fillColor = '#ee2a33';
Raphaël:
var paper = Raphael('raphaelCircle', 200, 200);
var c = paper.ellipse(100, 100, 10, 10);
c.attr({'fill': '#00aeef', 'stroke': '#00aeef'});
Processing.js:
void setup() {
size(200, 200);
}
void draw() {
background(#ffffff);
translate(100, 100);
fill(#52b755);
noStroke();
ellipse(0, 0, 20, 20);
}
Each code snippet draws the same circle. The difference is in what you can do with it.
Paper.js creates the circle as a path object. We can hold onto the object and change it later. In Paper.js, circle.fillColor = 'red';
fills our circle with red, and circle.scale(2)
makes it twice as big.
Raphaël follows Paper.js’ object-oriented model. In Raphaël, we can change the color of our circle with circle.attr('fill', 'red');
, and scale it up with circle.scale(2, 2);
. The point is that the circle is an object that we can work with later.
Processing.js doesn’t use objects; the ellipse
function doesn’t return anything. Once we’ve drawn our circle in Processing.js, it’s part of the rendered image, like ink on a page; it’s not a separate object that can be changed by modifying a property. To change the color, we have to draw a new circle directly on top of the old one.
When we call fill
, it changes the fill color for every object we draw thereafter. After we call translate
and fill
, every shape will be filled with green.
Because functions change everything, we can easily end up with unwanted side effects. Call a harmless function, and suddenly everything is green! Processing.js provides the pushMatrix
and popMatrix
functions to isolate changes, but you have to remember to call them.
Processing.js’ no-objects philosophy means that complex drawings run faster. Paper.js and Raphaël contain references to everything you draw, and so the memory overhead created by complex animations will slow down your application. Processing.js contains no references to drawn elements, so each shape takes up a tiny amount of memory. Memory overhead pays off if you need to access an object later, but it’s overkill if you don’t. Paper.js gives you a way out of this with the Symbol
object and by rasterizing objects, but you have to plan ahead to keep the app running fast.
The object-oriented versus no-objects philosophy has implications for everything you do with these libraries. It shapes the way each library handles animations.
Let’s Make It Move
Rotating circles aren’t very interesting, so we’ll make a square rotate around a circle.
Animation in Processing.js
Processing.js supports animation with the predefined setup
and draw
functions, like this:
float angle = 0.0;
void setup() {
size(200, 200);
frameRate(30);
}
void draw() {
background(#ffffff);
translate(100, 100);
fill(#52b755);
noStroke();
ellipse(0, 0, 20, 20);
rotate(angle);
angle += 0.1;
noFill();
stroke(#52b755);
strokeWeight(2);
rect(-40, -40, 80, 80);
}
The setup
function is called once when the application starts. We tell Processing.js to animate with a frame rate of 30 frames per second, so our draw
function will be called 30 times every second. That rate might sound high, but it’s normal for making an animation look smooth.
The draw
function starts by filling in the background of the canvas
; it paints over anything left over from previous invocations of the draw
function. This is a major difference with Processing.js: we are not manipulating objects, so we always have to clean up previously drawn shapes.
Next, we translate the coordinate system to the 100,100
point. This positions the drawing at 100 pixels from the left and 100 pixels from the top of the canvas for every drawing until we reset the coordinates. Then, we rotate by the specified angle. The angle increases with every draw
, which makes the square spin around. The last step is to draw a square using the fill
and rect
functions.
The rotate
function in Processing.js normally takes radians instead of degrees. That’s why we increase the angle of each frame by 0.2, rather than a higher number such as 3. This is one of many times when trigonometry shows up in this method of drawing.
Animation in Paper.js
Paper.js makes this simple animation easier than in Processing.js, with a persistent rectangle object:
var r;
function init() {
var c = new Path.Circle(new Point(100, 100), 10);
c.fillColor = '#ee2a33';
var point = new Point(60, 60);
var size = new Size(80, 80);
var rectangle = new Rectangle(point, size);
r = new Path.Rectangle(rectangle);
r.strokeColor = '#ee2a33';
r.strokeWidth = 2;
}
function onFrame(event) {
r.rotate(3);
}
init();
We maintain the state of our square as an object, and Paper.js handles drawing it on the screen. We rotate it a little for each frame. Paper.js manages the path, so we don’t have to redraw everything for each frame or keep track of the angle of rotation or worry about affecting other objects.
Animation in Raphaël
Animations in Raphaël are written in standard JavaScript, so Raphaël doesn’t have specific functions for handling animation frames. Instead, we rely on JavaScript’s setInterval
function.
var paper = Raphael('raphaelAnimation', 200, 200);
var c = paper.ellipse(100, 100, 10, 10);
c.attr({
'fill': '#00aeef',
'stroke': '#00aeef'
});
var r = paper.rect(60, 60, 80, 80);
r.attr({
'stroke-width': 2,
'stroke': '#00aeef'
});
setInterval(function() {
r.rotate(6);
}, 33);
Raphaël is similar to Paper.js in its object-oriented approach. We have a square, and we call a rotate
function on it. Thus, we can easily spin the square with a small amount of code.
Interaction
Raphaël shines when you need to enable interactivity in a drawing. It provides an event model similar to JavaScript’s, making it easy to detect clicks, drags and touches. Let’s make our square clickable.
Interactions With Raphaël
var paper = Raphael('raphaelInteraction', 200, 200);
var r = paper.rect(60, 60, 80, 80);
r.attr({'fill': '#00aeef', 'stroke': '#00aeef'});
var clicked = false;
r.click(function() {
if (clicked) {
r.attr({'fill': '#00aeef', 'stroke': '#00aeef'});
} else {
r.attr({'fill': '#f00ff0', 'stroke': '#f00ff0'});
}
clicked = !clicked;
});
The click
function in Raphaël works like jQuery, and you can add it to any object. Once we get the click event, changing the color of the square is easy. Raphaël has more functions to support dragging, hovering and all of the other user interactions you expect from JavaScript.
Interactions With Paper.js
Paper.js has a different way of managing interactions, but it’s still pretty easy:
var hitOptions = {
fill: true,
tolerance: 5
};
function init() {
var point = new Point(60, 60);
var size = new Size(80, 80);
var rectangle = new Rectangle(point, size);
r = new Path.Rectangle(rectangle);
r.fillColor = '#ee2a33';
}
function onMouseUp(event) {
var hitResult = project.hitTest(event.point, hitOptions);
if (hitResult && hitResult.item) {
if (hitResult.item.clicked) {
hitResult.item.fillColor = '#ee2a33';
} else {
hitResult.item.fillColor = '#f00ff0';
}
hitResult.item.clicked = !hitResult.item.clicked;
}
}
init();
Paper.js deals with mouse gestures through a concept called “hit testing.� A hit finds the point under the mouse cursor and figures out which object it lies above. Hit options enable you to define how the hit works: you can set options for such things as how close the mouse has to be, and whether the middle of the object counts or only the edge. We can extend this hit test to any object or group of objects in Paper.js.
The Paper.js team added object-level events similar to Raphaël’s a few weeks ago. The events should show up in the next release.
Interactions With Processing.js
Processing.js makes detecting mouse clicks tricky. It doesn’t support object-level events or hit testing, so we’re pretty much on our own.
float bx;
float by;
int bs = 20;
boolean bover = false;
boolean clicked = false;
void setup() {
size(200, 200);
bx = width/2.0;
by = height/2.0;
noStroke();
fill(#52b755);
frameRate(10);
}
void draw() {
background(#ffffff);
// Test if the cursor is over the box
if (mouseX > bx-bs && mouseX < bx+bs && mouseY > by-bs && mouseY < by+bs) {
bover = true;
} else {
bover = false;
}
translate(100, 100);
rect(-40, -40, 80, 80);
}
void mousePressed() {
if (bover) {
if (clicked) {
fill(#52b755);
} else {
fill(#f00ff0);
}
clicked = !clicked;
}
}
Once Processing.js draws the square, it forgets about it. We want the color of the square to change when we click on it, but the script doesn’t know that, so we have to do all of the calculations ourselves. The draw
function detects the mouse cursor’s position and does the math to determine whether it lies within the square.
The code is not too bad for the square, but our circle would need πr2
. And more complex shapes such as ovals, curves and compound shapes would require even more math.
No Clear Winner
Each framework has its advantages. Between them, the features make for cool demos and even cooler applications.
Showing Off Paper.js
Paper.js excels at manipulating complex shapes. It can turn, twist and transform any object in hundreds of ways. These transforms make it easy to convert objects based on interactive gestures. The new Google Music Tour, which makes colored lines beat in time to music, shows how one can make complex changes on simple shapes.
The other wow factor in Paper.js is its support of raster graphics. Paper.js can completely change the way images are drawn — including by turning them into spirals and Q*bert boards.
Showing Off Processing.js
Processing.js’ biggest feature is speed, making it possible to draw complex animations on slower machines. Many examples are out there, but the fluidity of Processing.js animations shows up best in Ricardo Sánchez’s koi pond.
The swishing of the tails and waving of the bodies make the koi look very natural. Processing.js makes this easy, with support for curves and customized animations.
Processing.js also supports complex drawing elements such as shading, lighting and 3-D transforms. If you want to create complex animations in canvas
very quickly, then Processing.js is the clear winner.
Showing Off Raphaël
The best feature of Raphaël is its support for Internet Explorer 7 and 8. If your application has to run on older browsers, then Raphaël is the only option.
The other big feature of Raphaël is its community. Raphaël is older than Paper.js and Processing.js and thus has had more time to build examples, tutorials and user support. It has built-in support for easing, animation transforms and the event handlers that we saw in the interaction example; it also has a comprehensive charting library.
Raphaël also has the best tooling support.
The Tools
If you’ve worked with Flash, the lack of tools for these frameworks will disappoint you. Many of the frameworks will edit SVG images, but none of them offer a drag-and-drop method for creating applications.
A few simple tools are out there, but they are more like proofs of concept than actual products. Adobe is working on a tool named Edge, but it has a long way to go.
If you want to drag and drop, then Web animations aren’t for you yet. Right now, this method of drawing is more like video-game programming. Writing code to draw a circle is tougher than clicking and dragging, but it scales to more complex applications and some fun stuff.
Let’s Build Something Real
So far, we’ve looked at some simple examples, seen the best features of each platform and looked at how to choose the right one. Each framework has pluses and minuses, but judging them is difficult until you create an actual application.
To compare each framework, I’ve drawn some gears. Each gear is made up of two circles, with a set of teeth around the outer circle.
When the shapes are all given the same color, they look just like a gear.
Every gear will rotate a little with each frame of the animation. The first gear will be given a speed, and the rest will move relative to it. The gears will arrange, mesh and rotate together with a crazy amount of trigonometry. Put them together and you’ve got a complex gear system.
Paper.js:
Processing.js:
Raphaël:
Well, that wasn’t quite Raphaël. The rotate
function work different in Raphaël than it does in Paper.js and Processing.js. Raphaël doesn’t support rotation around a fixed point. Instead, the teeth of the gears are drawn and redrawn independently, and they fly through the air instead of rotating around the center. The only way to really turn the gear would be to draw the entire gear as a single path, and that takes more math than I’m willing to write. If anyone wants to give it a try, everything is open source.
The Future Of Web Drawing
We gamble on every new technology that we learn: we hope that it catches on and that our investment pays off. Technologies rise and fall on their respective merits, but other factors comes into play, such as vendor support and business uses. The future of our industry is almost a guessing game.
Right now, Flash looks like a bad investment. Flash has great tools, years of development and a large community, but even Adobe is moving away from it.
SVG is in a similar situation. Browsers support it now, but it isn’t getting a lot of attention.
Every browser vendor is working hard to render canvas
faster, to use hardware acceleration and to better support libraries such as Paper.js and Processing.js. All mobile devices support canvas
, and their developers are working to improve it.
(al)
© Zack Grossbart for Smashing Magazine, 2012.