Raphaël: a JavaScript API for SVG
Introduction
The first time I saw SVG in 2000–2001, I was blown away by the power it has, and the simplicity of the language, but I was also dismayed that I couldn't really make use of it, because browsers didn't support it. These days all major browsers (with one Important Exception) support SVG to a reasonable extent. This means we can now start to play with it and make use of it on some sites, but Flash is still more popular for vector graphics among front end developers. Why? Because few people know how to work with SVG; In general people coding dynamic applications are much more familiar with JavaScript (or ActionScript).
To solve both the compatibility issues and the knowledge gap, I decided to create Raphaël. This is a JavaScript library that provides an API for manipulating SVG, and SVG support for Internet Explorer. It achieves the latter by emulating SVG in Internet Explorer using VML. You don't need to know SVG to work with this library, but SVG knowledge is certainly a bonus, so I'd suggest you get up to speed with the basics if you find the time.
A simple first example
Lets see how it all works by looking at a simple example - let's create a typical "progress throbber", as seen in Apple interfaces, and copied by many. It is often used in collaboration with Ajax, or complex calculations on the client side. A progress throbber usually looks like that seen in Figure 1:
Figure 1: A typical progress throbber.
Let's recreate such a throbber without using any images - just SVG via Raphaël.
The HTML
First things first - the HTML file for our example (spinner.html in the code download) is very simple:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Spinner</title>
<script src="raphael.js" type="text/javascript" charset="utf-8"></script>
<script src="spinner.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="holder"></div>
</body>
</html>
The body
contains a simple div
, which in turn will contain our spinner. The only other thing to note about it is that I have linked it to the library file, raphael.js
, using a script
element in the head
, and then done the same for spinner.js
. spinner.js
is our custom script, and I will cover it below.
I prefer to create the container for such graphics in HTML, because it is easier to control it later with CSS, fitting it into layouts nicely, etc. Feel free to add some CSS to make the example look prettier.
Coding
Next, let's take a look at our spinner.js
code:
window.onload = function () {
var r = Raphael("holder", 600, 600),
sectorsCount = 12, // number of dashes in spinner
color = "#000", // throbber colour
width = 15, // width of the dashes
r1 = 35, // inner radius of the spinner
r2 = 60, // outer radius of the spinner
cx = 300, // x and y of the centre of the spinner
cy = 300,
The script is placed inside a window.onload
event handler. At the start of it we create a couple of useful variables. Most are self-explanatory (see the comments above); r
is an instance of Raphaël, created inside the "holder" div
and given dimensions of 600 x 600 pixels.
The next part of spinner.js
is as follows:
sectors = [], // array for dashes
opacity = [], // array for the opacity of the dashes
beta = 2 * Math.PI / sectorsCount, // angle between dashes
Here we have defined two arrays to help us manage the dashes of the spinner and calculate the angle between dashes so we don't need to calculate it again later.
pathParams = {stroke: color, "stroke-width": width, "stroke-linecap": "round"};
The last variable stores properties for each dash: stroke colour, stroke width and stroke line cap. Now we get to the real business code:
for (var i = 0; i < sectorsCount; i++) {
var alpha = beta * i - Math.PI / 2, // angle between current dash and initial state
cos = Math.cos(alpha),
sin = Math.sin(alpha);
opacity[i] = 1 / sectorsCount * i; // initial opacity for current dash
sectors[i] = r.path(pathParams) // new path in Raphaël
.moveTo(cx + r1 * cos, cy + r1 * sin) // move to point on inner radius
.lineTo(cx + r2 * cos, cy + r2 * sin); // line to point on outer radius
}
In a simple cycle we calculate all the future dashes to be displayed. Depending on the dash count we calculate the angle of the current dash and its trigonometric values, then we calculate the appropriate opacity, so the opacity of the dashes degrades from 1 to 0 as we go round the circle. Finally, in the sectors
array we add the newly created path. In the case of this example it quite simple - it is a straight line from each angle on the inner circle to the same point on the same angle on the outer circle. By this point the code produces something like Figure 2.
Figure 2: The dashes are now created, but we still have to animate them and add the opacity.
As you can see, opacity is currently stored in the array, but it hasn't yet been applied to our dashes. Let's do this, then animate our spinner:
(function ticker() {
opacity.unshift(opacity.pop());
for (var i = 0; i < sectorsCount; i++) {
sectors[i].attr("opacity", opacity[i]); // set new opacity attribute
}
r.safari(); // temporary (hopefully) fix for Safari
setTimeout(ticker, 1000 / sectorsCount);
})();
In this code we create a function called ticker
and run it immediately. The first line of the function shifts elements in the opacity array forward. We then run over dashes stored in the sectors
array and apply an opacity
attribute to each of them. Doing this continuously will make an illusion of animation. r.safari();
fixes some rendering bugs in Safari.
At the end of the function we set timeout
to run it again after a small amount of time, giving the appearence of the spinner's continuous rotation. It will make it run forever like a clock.
The only thing left to do is to close the function, like so:
};
The complete code example looks like this:
window.onload = function () {
var r = Raphael("holder", 600, 600),
sectorsCount = 12,
color = "#000",
width = 15,
r1 = 35,
r2 = 60,
cx = 300,
cy = 300,
sectors = [],
opacity = [],
beta = 2 * Math.PI / sectorsCount,
pathParams = {stroke: color, "stroke-width": width, "stroke-linecap": "round"};
for (var i = 0; i < sectorsCount; i++) {
var alpha = beta * i - Math.PI / 2,
cos = Math.cos(alpha),
sin = Math.sin(alpha);
opacity[i] = 1 / sectorsCount * i;
sectors[i] = r.path(pathParams)//.attr("stroke", Raphael.getColor())
.moveTo(cx + r1 * cos, cy + r1 * sin)
.lineTo(cx + r2 * cos, cy + r2 * sin);
}
(function ticker() {
opacity.unshift(opacity.pop());
for (var i = 0; i < sectorsCount; i++) {
sectors[i].attr("opacity", opacity[i]);
}
r.safari();
setTimeout(ticker, 1000 / sectorsCount);
})();
};
This is pretty short and simple. The benefit of this method is that you can change foreground and background colours easily as you wish (foreground colour is specified in the script via the color
variable, and background colour is just the background of the HTML page), and the throbber has true opacity, so you can put it seamlessly on top of anything you like. As a bonus try uncommenting the commented line. Check out the live demo right now, if you fancy.
If you wanted to recreate the same example in pure SVG it would look like this:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke-width="15" stroke-linecap="round" stroke="#000" transform="translate(100, 100)">
<animateTransform attributeName="transform" attributeType="XML" calcMode="discrete"
additive="sum" type="rotate" values="30;60;90;120;150;180;210;240;270;300;330;360"
dur="1s" repeatCount="indefinite"/>
<line x1="0" y1="-35" x2="0" y2="-60"/>
<line x1="0" y1="-35" x2="0" y2="-60" transform="rotate(30, 0, 0)" opacity=".08"/>
<line x1="0" y1="-35" x2="0" y2="-60" transform="rotate(60, 0, 0)" opacity=".16"/>
<line x1="0" y1="-35" x2="0" y2="-60" transform="rotate(90, 0, 0)" opacity=".25"/>
<line x1="0" y1="-35" x2="0" y2="-60" transform="rotate(120, 0, 0)" opacity=".33"/>
<line x1="0" y1="-35" x2="0" y2="-60" transform="rotate(150, 0, 0)" opacity=".42"/>
<line x1="0" y1="-35" x2="0" y2="-60" transform="rotate(180, 0, 0)" opacity=".5"/>
<line x1="0" y1="-35" x2="0" y2="-60" transform="rotate(210, 0, 0)" opacity=".58"/>
<line x1="0" y1="-35" x2="0" y2="-60" transform="rotate(240, 0, 0)" opacity=".67"/>
<line x1="0" y1="-35" x2="0" y2="-60" transform="rotate(270, 0, 0)" opacity=".75"/>
<line x1="0" y1="-35" x2="0" y2="-60" transform="rotate(300, 0, 0)" opacity=".83"/>
<line x1="0" y1="-35" x2="0" y2="-60" transform="rotate(330, 0, 0)" opacity=".9"/>
</g>
</svg>
All the parameters are hard coded into the SVG markup, so it wouldn't be as easy to change the number of dashes from say 12 to 16, but you will probably agree that this example is simpler to create using pure SVG than Raphaël JS (well, provided you know SVG syntax). The only serious problem with this example is that it wouldn't work in Internet Explorer and Firefox (at the time of writing Firefox doesn't support animation in SVG). Raphaël code however will work everywhere.
Summary
So that's it for your short initial look into the world of Raphaël - I hope this has demonstrated why this is a useful project to check out. For more examples, go to the official Raphaël web site - feel free to hack some of the demos and cook your own coolness.
This article is licensed under a Creative Commons Attribution, Non Commercial - Share Alike 2.5 license.
Comments
The forum archive of this article is still available on My Opera.