How to simplify SVG with Raphael

Bitmaps are by far the most commonly used image format on web pages. When the images we want to show are photos, that makes sense.  But when it comes to illustrations and GUI components, bitmaps are wasteful.

Many designers actually do make extensive use of bitmap images directly in user interfaces. Personally I don’t feel this is a good approach, but the blame for it becoming habitual stems from the extensive use and recommendation of Photoshop for interface design.

Vector graphics offer numerous advantages over bitmaps in interface design because:

  • they usually have a low bandwidth overhead compared with bitmaps;
  • they are inherently scalable / responsive;
  • they can be easily animated;
  • they can be interactive.

Enter SVG

SVG does not require a separate embedded player to work, and does not expose users to any known security vulnerabilities. It’s not a proprietary technology. Best of all, SVG fits directly into your document and doesn’t need to be an embedded object, giving you full control over every part of it. The one unfortunate drawback is that SVG comes with a steep learning curve, and so…

Enter Raphael

Raphael is a JavaScript library that makes the process of programming in SVG a lot simpler than doing it natively. Of course you could also use dedicated SVG drawing software such as Inkscape to create your images; but I’m a fan of full control, and Raphael gives you that. As an image becomes more complex, drawing programs tend to add in a lot of extra things that you didn’t really ask for, which can be a pain to clean up.

Some of the advantages you get with Raphael include:

  • compatible with HTML4, XHTML, and HTML5;
  • works in all common browsers except very old versions of IE (which don’t support SVG anyway);
  • greatly simplifies the process of SVG coding.

Now let’s take a look at a real-life implementation of an application developed in Raphael so you can see just how easy and powerful this method is..

Getting started with Raphael

An MCDU is a computer interface used in Airbus aircraft to give access to the navigation computer and other things like that. We’re going to draw one with SVG. You can see a working example of the finished project by heading over to equicom.net/mcdu

Getting started with Raphael means we need to obtain the Raphael Library, but this is not a difficult task. It is similar to including other libraries like jQuery, which you’ve probably already done thousands of times before. So before doing anything else, we download the file from here: http://github.com/DmitryBaranovskiy/raphael/raw/master/raphael-min.js.

Somewhere in the body of your HTML document (usually at the end), you can place a call to:

<script src="raphael-min.js"></script>

Now that you have added this line, your document is fully configured for using Raphael to draw and animate!

Then, at whatever point in the document we want to make our drawing appear, we add the following lines:

<div id="canvas">
  <div id="paper">
  </div>
</div>

Then you need to think up a name for the object that will be manipulated in the drawing context. For the MCDU project the obvious choice is “MCDU” and you just declare it as an empty array inside a script like this:

var MCDU={};

Then you need to add your Raphael object, like this:

var R=Raphael("paper",500,700);

Those parameters are important. The first one tells which div will receive the drawing object (the “paper” div we defined earlier) and the height and width for the object in pixels. It is a good policy to make it larger than you need to begin with, and then adjust it later when you are satisfied with the outcome.

You won’t see anything yet because we have to send a drawing command. Before I do that, I will just add some quick variables for different attributes to save me from having to do a lot of typing later.

var bodyAttr={fill:"#AAAAAA",stroke:"#000000","stroke-width":1,"stroke-linejoin": "round"};

Note the quote marks, these are important!  The reason why quotes are around property names as well as values is due to the minus sign being included in the name. There are many other similar attributes defined, but I have just provided this one as an example.

Here’s how we make the background of the MCDU device:

MCDU.body = R.rect(0,0,450,610).attr(bodyAttr);

This adds a property to the MCDU object called “body” and defines it as a Raphael Rectangle object (rect) with X and Y co-ordinates for the top left corner (0,0), followed by the width (450) and height (610) .  If you refer to the pre-defined attribute, you can see that the fill color is set to a dark gray, stroke is a simple 1px black line, and the join between lines is set to round.

Here is what that looks like (scaled to fit in this window, of course!):

Very simple, quick, and effective.  The next step is to add the status lights that will appear at the top of the device (the statusAttr variable is defined elsewhere, see the full source code at the end of this article).

MCDU.status1=R.rect(80,5,40,10,4).attr(statusAttr);
MCDU.status2=R.rect(140,5,40,10,4).attr(statusAttr);
MCDU.status3=R.rect(200,5,40,10,4).attr(statusAttr);
MCDU.status4=R.rect(260,5,40,10,4).attr(statusAttr);
MCDU.status5=R.rect(320,5,40,10,4).attr(statusAttr);

The extra parameter added is the radius of the rounded corners, in this case 4px.

And add the text to them.

MCDU.st1Text=R.text(100,10,"FM1").attr(dispAmTextAttr);
MCDU.st2Text=R.text(160,10,"IND").attr(dispAmTextAttr);
MCDU.st3Text=R.text(220,10,"RDY").attr(dispGnTextAttr);
MCDU.st4Text=R.path("M265 10L295 10").attr(miscAmberAttr);
MCDU.st5Text=R.text(340,10,"FM2").attr(dispAmTextAttr);

Each of the parameters is X, Y, text, attributes. The odd one out is item 4 where we have a path (line) instead of text. In this case we are saying Move to 265,10 then make a Line to 295,10. Paths can be as complex as you want, and can include multiple move and line commands.

Add left selection keys:

MCDU.LSK01=R.rect(4,42,30,15,6).attr(LSK_Attr);
MCDU.LSK_Groove01=R.path("M10 49L28 49").attr(LSK_GrooveAttr);
MCDU.LSK02=R.rect(4,72,30,15,6).attr(LSK_Attr);
MCDU.LSK_Groove02=R.path("M10 79L28 79").attr(LSK_GrooveAttr);
MCDU.LSK03=R.rect(4,102,30,15,6).attr(LSK_Attr);
MCDU.LSK_Groove03=R.path("M10 109L28 109").attr(LSK_GrooveAttr);
MCDU.LSK04=R.rect(4,132,30,15,6).attr(LSK_Attr);
MCDU.LSK_Groove04=R.path("M10 139L28 139").attr(LSK_GrooveAttr);
MCDU.LSK05=R.rect(4,162,30,15,6).attr(LSK_Attr);
MCDU.LSK_Groove05=R.path("M10 169L28 169").attr(LSK_GrooveAttr);
MCDU.LSK06=R.rect(4,192,30,15,6).attr(LSK_Attr);
MCDU.LSK_Groove06=R.path("M10 199L28 199").attr(LSK_GrooveAttr);

Then we create a space for the screen to sit in. The MCDU screen is called a scratchpad, and to get a 3D effect we need to sit it in a “well”.

MCDU.scratchWell=R.rect(39,20,365,210,10).attr(scratchWellAttr);

Add some guidelines for the buttons.

MCDU.LSK_Guide01=R.path("M39 49L56 54").attr(LSK_Guide_Attr);
MCDU.LSK_Guide02=R.path("M39 79L56 84").attr(LSK_Guide_Attr);
MCDU.LSK_Guide03=R.path("M39 109L56 114").attr(LSK_Guide_Attr);
MCDU.LSK_Guide04=R.path("M39 139L56 144").attr(LSK_Guide_Attr);
MCDU.LSK_Guide05=R.path("M39 169L56 174").attr(LSK_Guide_Attr);
MCDU.LSK_Guide06=R.path("M39 199L56 204").attr(LSK_Guide_Attr);
MCDU.LSK_Guide07=R.path("M405 49L388 54").attr(LSK_Guide_Attr);
MCDU.LSK_Guide08=R.path("M405 79L388 84").attr(LSK_Guide_Attr);
MCDU.LSK_Guide09=R.path("M405 109L388 114").attr(LSK_Guide_Attr);
MCDU.LSK_Guide10=R.path("M405 139L388 144").attr(LSK_Guide_Attr);
MCDU.LSK_Guide11=R.path("M405 169L388 174").attr(LSK_Guide_Attr);
MCDU.LSK_Guide12=R.path("M405 199L388 204").attr(LSK_Guide_Attr);

Add the selection keys for the right hand side.

MCDU.LSK07=R.rect(410,42,30,15,6).attr(LSK_Attr);
MCDU.LSK_Groove07=R.path("M416 49L434 49").attr(LSK_GrooveAttr);
MCDU.LSK08=R.rect(410,72,30,15,6).attr(LSK_Attr);
MCDU.LSK_Groove08=R.path("M416 79L434 79").attr(LSK_GrooveAttr);
MCDU.LSK09=R.rect(410,102,30,15,6).attr(LSK_Attr);
MCDU.LSK_Groove09=R.path("M416 109L434 109").attr(LSK_GrooveAttr);
MCDU.LSK10=R.rect(410,132,30,15,6).attr(LSK_Attr);
MCDU.LSK_Groove10=R.path("M416 139L434 139").attr(LSK_GrooveAttr);
MCDU.LSK11=R.rect(410,162,30,15,6).attr(LSK_Attr);
MCDU.LSK_Groove11=R.path("M416 169L434 169").attr(LSK_GrooveAttr);
MCDU.LSK12=R.rect(410,192,30,15,6).attr(LSK_Attr);
MCDU.LSK_Groove12=R.path("M416 199L434 199").attr(LSK_GrooveAttr);

Then we add the actual scratchpad on top.

MCDU.scratch=R.rect(47,25,350,200,10).attr(scratchAttr);

We’ll skip a few steps from the real project and go straight to adding the power switch / brightness control (called “dimmer” here).

MCDU.dimmer=R.circle(405,258,12).attr(dimmerAttr);
MCDU.dimmerGuideA=R.path("M405 258L417 250").attr({opacity:0});
MCDU.dimmerGuideB=R.path("M405 258L390 258").attr({opacity:1});
MCDU.dimmerText1=R.text(430,245,"BRT").attr(keySmallAttr);
MCDU.dimmerText2=R.text(374,264,"OFF").attr(keySmallAttr);

What we have introduced here is the circle method, and also the opacity attribute. Parameters of the circle method are simply the X,Y point of the center of the circle, and the radius of the circle.  Opacity is the percentage of opacity, so 0 is transparent and 1 is totally opaque. Therefore 0.5 is 50% transparent.

Now for the final step of our example, we will introduce some interactivity. Let’s turn off all the lights and display a “Hello World” message on the scratchpad when the user clicks BRT (and do the opposite when they click OFF).

So let’s start by making the HELLO WORLD message:

MCDU.textExample=R.text(225,60,"HELLO WORLD!!").attr(dispYwTitleAttr);

And we’ll have to hide it so that it’s not there until we want it to be:

MCDU.textExample.animate({opacity:0});

Because our SVG components are all in the DOM, we can use standard JavaScript methods to respond to events. Here we will use the onClick event to detect a user action:

MCDU.dimmerText1.node.onclick = function() {
  MCDU.dimmerGuideA.animate({opacity:1});
  MCDU.dimmerGuideB.animate({opacity:0});
  MCDU.st1Text.animate({opacity:0.2});
  MCDU.st2Text.animate({opacity:0.2});
  MCDU.st3Text.animate({opacity:0.2});
  MCDU.st4Text.animate({opacity:0.2});
  MCDU.st5Text.animate({opacity:0.2});
  MCDU.textExample.animate({opacity:1});
}

MCDU.dimmerText2.node.onclick = function() {
  MCDU.dimmerGuideA.animate({opacity:0});
  MCDU.dimmerGuideB.animate({opacity:1});
  MCDU.st1Text.animate({opacity:1});
  MCDU.st2Text.animate({opacity:1});
  MCDU.st3Text.animate({opacity:1});
  MCDU.st4Text.animate({opacity:1});
  MCDU.st5Text.animate({opacity:1});
  MCDU.textExample.animate({opacity:0});
}

Really simple stuff, as you can see. You may even be disappointed because it was too easy! For more inspiration, you can visit http://raphaeljs.com/ and check out the examples.

The full code

<html>
    <head>
    </head>
    <body>
        <div id="canvas">
            <div id="paper">
            </div>
        </div>
        <script src="raphael-min.js"></script>
        <script>
            var MCDU={};
            var R=Raphael("paper",500,700);
            var bodyAttr={fill:"#AAAAAA",stroke:"#000","stroke-width":1,"stroke-linejoin":"round"};
            var statusAttr={fill:"#111",stroke:"#666","stroke-width":1,"stroke-linejoin":"round"};
            var LSK_Attr={fill:"#333333",stroke:"#000","stroke-width":1,"stroke-linejoin":"round"};
            var LSK_GrooveAttr={stroke:"#FFFFFF","stroke-width":3};
            var LSK_Guide_Attr={stroke:"#FFFFFF","stroke-width":2};
            var scrWellAttr={fill:"#777",stroke:"#666","stroke-width":1,"stroke-linejoin": "round"};
            var scrAttr={fill:"#000000",stroke:"#000000","stroke-width":1,"stroke-linejoin": "round"};
            var dispWtTextAttr={fill:"#FFFFFF","font-size":12,"font-family":"monospace"};
            var dispWtTitleAttr={fill:"#FFFFFF","font-size":14,"font-family":"monospace"};
            var dispWtSmallAttr={fill:"#FFFFFF","font-size":10,"font-family":"monospace"};
            var dispGnTextAttr={fill:"#00FF00","font-size":12,"font-family":"monospace"};
            var dispGnTitleAttr={fill:"#00FF00","font-size":14,"font-family":"monospace"};
            var dispGnSmallAttr={fill:"#00FF00","font-size":10,"font-family":"monospace"};
            var dispCyTextAttr={fill:"#00FFFF","font-size":12,"font-family":"monospace"};
            var dispCySmallAttr={fill:"#00FFFF","font-size":10,"font-family":"monospace"};
            var dispCyTitleAttr={fill:"#00FFFF","font-size":14,"font-family":"monospace"};
            var miscCyanAttr={stroke:"#00FFFF"};
            var dispAmTextAttr={fill:"#FF8800","font-size":12,"font-family":"monospace"};
            var dispAmTitleAttr={fill:"#FF8800","font-size":14,"font-family":"monospace"};
            var dispAmSmallAttr={fill:"#FF8800","font-size":10,"font-family":"monospace"};
            var miscAmberAttr={stroke:"#FF8800"};
            var dispMgTextAttr={fill:"#FF00FF","font-size":12,"font-family":"monospace"};
            var dispMgTitleAttr={fill:"#FF00FF","font-size":14,"font-family":"monospace"};
            var dispMgSmallAttr={fill:"#FF00FF","font-size":10,"font-family":"monospace"};
            var dispYwTextAttr={fill:"#FFFF00","font-size":12,"font-family":"monospace"};
            var dispYwTitleAttr={fill:"#FFFF00","font-size":14,"font-family":"monospace"};
            var dispYwSmallAttr={fill:"#FFFF00","font-size":10,"font-family":"monospace"};
            var keyTextAttr={fill:"#FFFFFF","font-size":14};
            var keySmallAttr={fill:"#FFFFFF","font-size":12};
            var keyBigAttr={fill:"#FFFFFF","font-size":20};
            var keyGiantAttr={fill:"#FFFFFF","font-size":40};
            var dimmerAttr={fill:"#BBBBBB"};
            var arrowAttr={stroke:"#FFFFFF",fill:"#FFFFFF","stroke-width":2};
            var kbdSepAttr={fill:"#888",stroke:"#888","stroke-width":1,"stroke-linejoin":"round"};
            var gyLineAttr={stroke:"#999"};
            MCDU.body=R.rect(0,0,450,610).attr(bodyAttr);
            MCDU.status1=R.rect(80,5,40,10,4).attr(statusAttr);
            MCDU.status2=R.rect(140,5,40,10,4).attr(statusAttr);
            MCDU.status3=R.rect(200,5,40,10,4).attr(statusAttr);
            MCDU.status4=R.rect(260,5,40,10,4).attr(statusAttr);
            MCDU.status5=R.rect(320,5,40,10,4).attr(statusAttr);
            MCDU.st1Text=R.text(100,10,"FM1").attr(dispAmTextAttr);
            MCDU.st2Text=R.text(160,10,"IND").attr(dispAmTextAttr);
            MCDU.st3Text=R.text(220,10,"RDY").attr(dispGnTextAttr);
            MCDU.st4Text=R.path("M265 10L295 10").attr(miscAmberAttr);
            MCDU.st5Text=R.text(340,10,"FM2").attr(dispAmTextAttr);
            MCDU.LSK01=R.rect(4,42,30,15,6).attr(LSK_Attr);
            MCDU.LSK_Groove01=R.path("M10 49L28 49").attr(LSK_GrooveAttr);
            MCDU.LSK02=R.rect(4,72,30,15,6).attr(LSK_Attr);
            MCDU.LSK_Groove02=R.path("M10 79L28 79").attr(LSK_GrooveAttr);
            MCDU.LSK03=R.rect(4,102,30,15,6).attr(LSK_Attr);
            MCDU.LSK_Groove03=R.path("M10 109L28 109").attr(LSK_GrooveAttr);
            MCDU.LSK04=R.rect(4,132,30,15,6).attr(LSK_Attr);
            MCDU.LSK_Groove04=R.path("M10 139L28 139").attr(LSK_GrooveAttr);
            MCDU.LSK05=R.rect(4,162,30,15,6).attr(LSK_Attr);
            MCDU.LSK_Groove05=R.path("M10 169L28 169").attr(LSK_GrooveAttr);
            MCDU.LSK06=R.rect(4,192,30,15,6).attr(LSK_Attr);
            MCDU.LSK_Groove06=R.path("M10 199L28 199").attr(LSK_GrooveAttr);
            MCDU.scratchWell=R.rect(39,20,365,210,10).attr(scratchWellAttr);
            MCDU.LSK_Guide01=R.path("M39 49L56 54").attr(LSK_Guide_Attr);
            MCDU.LSK_Guide02=R.path("M39 79L56 84").attr(LSK_Guide_Attr);
            MCDU.LSK_Guide03=R.path("M39 109L56 114").attr(LSK_Guide_Attr); 
            MCDU.LSK_Guide04=R.path("M39 139L56 144").attr(LSK_Guide_Attr); 
            MCDU.LSK_Guide05=R.path("M39 169L56 174").attr(LSK_Guide_Attr); 
            MCDU.LSK_Guide06=R.path("M39 199L56 204").attr(LSK_Guide_Attr); 
            MCDU.LSK_Guide07=R.path("M405 49L388 54").attr(LSK_Guide_Attr);
            MCDU.LSK_Guide08=R.path("M405 79L388 84").attr(LSK_Guide_Attr); 
            MCDU.LSK_Guide09=R.path("M405 109L388 114").attr(LSK_Guide_Attr); 
            MCDU.LSK_Guide10=R.path("M405 139L388 144").attr(LSK_Guide_Attr); 
            MCDU.LSK_Guide11=R.path("M405 169L388 174").attr(LSK_Guide_Attr); 
            MCDU.LSK_Guide12=R.path("M405 199L388 204").attr(LSK_Guide_Attr);
            MCDU.LSK07=R.rect(410,42,30,15,6).attr(LSK_Attr);
            MCDU.LSK_Groove07=R.path("M416 49L434 49").attr(LSK_GrooveAttr);
            MCDU.LSK08=R.rect(410,72,30,15,6).attr(LSK_Attr);
            MCDU.LSK_Groove08=R.path("M416 79L434 79").attr(LSK_GrooveAttr);
            MCDU.LSK09=R.rect(410,102,30,15,6).attr(LSK_Attr);
            MCDU.LSK_Groove09=R.path("M416 109L434 109").attr(LSK_GrooveAttr);
            MCDU.LSK10=R.rect(410,132,30,15,6).attr(LSK_Attr);
            MCDU.LSK_Groove10=R.path("M416 139L434 139").attr(LSK_GrooveAttr);
            MCDU.LSK11=R.rect(410,162,30,15,6).attr(LSK_Attr);
            MCDU.LSK_Groove11=R.path("M416 169L434 169").attr(LSK_GrooveAttr);
            MCDU.LSK12=R.rect(410,192,30,15,6).attr(LSK_Attr);
            MCDU.LSK_Groove12=R.path("M416 199L434 199").attr(LSK_GrooveAttr);
            MCDU.scratch=R.rect(47,25,350,200,10).attr(scratchAttr);
            MCDU.dimmer=R.circle(405,258,12).attr(dimmerAttr);
            MCDU.dimmerGuideA=R.path("M405 258L417 250").attr({opacity:0});
            MCDU.dimmerGuideB=R.path("M405 258L390 258").attr({opacity:100}); 
            MCDU.dimmerText1=R.text(430,245,"BRT").attr(keySmallAttr);
            MCDU.dimmerText2=R.text(374,264,"OFF").attr(keySmallAttr);
            MCDU.textExample=R.text(225,60,"HELLO WORLD!!!").attr(dispYwTitleAttr);
            MCDU.textExample.animate({opacity:0});
            MCDU.dimmerText1.node.onclick = function() {
            MCDU.dimmerGuideA.animate({opacity:1});
            MCDU.dimmerGuideB.animate({opacity:0});
                MCDU.st1Text.animate({opacity:0.2});
                MCDU.st2Text.animate({opacity:0.2});
                MCDU.st3Text.animate({opacity:0.2});
                MCDU.st4Text.animate({opacity:0.2});
                MCDU.st5Text.animate({opacity:0.2});
                MCDU.textExample.animate({opacity:1});
            }
            MCDU.dimmerText2.node.onclick = function() {
                MCDU.dimmerGuideA.animate({opacity:0});
                MCDU.dimmerGuideB.animate({opacity:1});
                MCDU.st1Text.animate({opacity:1});
                MCDU.st2Text.animate({opacity:1});
                MCDU.st3Text.animate({opacity:1});
                MCDU.st4Text.animate({opacity:1});
                MCDU.st5Text.animate({opacity:1});
                MCDU.textExample.animate({opacity:0});
            }
        </script>
    </body>
</html>

Emma Grant is a professional freelance content writer from Ireland. Over the past three years she has travelled the world while running her business from her laptop. You find her at www.florencewritinggale.com More articles by Emma Grant
Home CSS Deals HTML HTML5 Java JavaScript jQuery Miscellaneous Mobile MySQL News PHP Resources Security Snippet Tools Tutorial Web Development Web Services WordPress