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 amongst front end developers. Why? Because few people know how to work with SVG; far more 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 is 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 often uses in collaboration with Ajax, or some 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 just holds a simple div to contain our spinner. The only other thing to note about it is that I have linked 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. Fill free to add some CSS over the top 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 it’s 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 adding new 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. Lets 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, to make the spinner keep rotating. 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 (the foreground colour is specified in the script as color variable and background colour is just 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 we would like 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 mark up, so it wouldn't be as easy to change number of dashes from say 12 to 16, but you should agree in SVG this example is simpler than the Raphaël JS example. The only serious problem with this example is 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.
About this article
| Published in | Dev.Opera |
|---|---|
| Date | January 15, 2009 |
| Author | Dmitry Baranovskiy |
| Tags | VML, trigonometry, animation, html, IE |
| Original | Internet Archive |