Web-Drawing Throwdown: Paper.js Vs. Processing.js Vs. Raphael

转载 2012年03月26日 17:07:40

Before drawing anything in a browser, ask yourself three questions:

  1. Do you need to support older browsers?
    If the answer is yes, then your only choice isRaphaë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.
  2. 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.
  3. 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.

[Note: Have you already pre-ordered your copy of our Printed Smashing Book #3? The book is a professional guide on how to redesign websites and it also introduces a whole new mindset for progressive Web design, written by experts for you.]

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

1 <head>
2 <script src="paper.js" type="text/javascript" charset="utf-8"></script>
3 <script type="text/paperscript" canvas="paperCircle" src="paper_circle.pjs" id="script"></script>
4 </head>
5 <body>
6 <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

1 <head>
2 <script src="processing.js" type="text/javascript" charset="utf-8"></script>
3 </head>
4 <body>
5 <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

1 <head>
2 <script src="raphael-min.js" type="text/javascript" charset="utf-8"></script>
3 <script src="raphael_circle.js" type="text/javascript" charset="utf-8"></script>
4 </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:

1 var circle = new Path.Circle(new Point(100, 100), 10);
2 circle.fillColor = '#ee2a33';

Raphaël:

1 var paper = Raphael('raphaelCircle', 200, 200);
2 var c = paper.ellipse(100, 100, 10, 10);
3 c.attr({'fill':'#00aeef','stroke':'#00aeef'});

Processing.js:

01 void setup() {
02    size(200, 200);
03 }
04  
05 void draw() {
06    background(#ffffff);
07    translate(100, 100);
08    fill(#52b755);
09    noStroke();
10    ellipse(0, 0, 20, 20);
11 }

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 calltranslate 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 andpopMatrix 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 theSymbol 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:

01 float angle = 0.0;
02 void setup() {
03    size(200, 200);
04    frameRate(30);
05 }
06  
07 void draw() {
08    background(#ffffff);
09    translate(100, 100);
10    fill(#52b755);
11    noStroke();
12    ellipse(0, 0, 20, 20);
13  
14    rotate(angle);
15    angle += 0.1;
16    noFill();
17    stroke(#52b755);
18    strokeWeight(2);
19    rect(-40, -40, 80, 80);
20 }

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:

01 var r;
02  
03 function init() {
04    var c = new Path.Circle(new Point(100, 100), 10);
05    c.fillColor = '#ee2a33';
06  
07    var point = new Point(60, 60);
08    var size = new Size(80, 80);
09    var rectangle = new Rectangle(point, size);
10    r = new Path.Rectangle(rectangle);
11    r.strokeColor = '#ee2a33';
12    r.strokeWidth = 2;
13 }
14  
15 function onFrame(event) {
16    r.rotate(3);
17 }
18  
19 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.

01 var paper = Raphael('raphaelAnimation', 200, 200);
02 var c = paper.ellipse(100, 100, 10, 10);
03 c.attr({
04    'fill':'#00aeef',
05    'stroke':'#00aeef'
06 });
07  
08 var r = paper.rect(60, 60, 80, 80);
09 r.attr({
10    'stroke-width': 2,
11    'stroke':'#00aeef'
12 });
13  
14 setInterval(function() {
15    r.rotate(6);
16 }, 33);

Raphaël is similar to Paper.js in its object-oriented approach. We have a square, and we call a rotatefunction 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

01 var paper = Raphael('raphaelInteraction', 200, 200);
02 var r = paper.rect(60, 60, 80, 80);
03 r.attr({'fill':'#00aeef','stroke':'#00aeef'});
04  
05 var clicked = false;
06  
07 r.click(function() {
08    if (clicked) {
09       r.attr({'fill':'#00aeef','stroke':'#00aeef'});
10    }else {