Click here to Skip to main content
Click here to Skip to main content
Go to top

Animations in HTML5

, 22 Jan 2012
Rate this:
Please Sign up or sign in to vote.
Exploring possibilities by comparing the HTML5 canvas element with the possibilities of CSS3 animations
We are going to animate a moving car with two different HTML5 techniques.

Introduction

HTML5 is becoming more and more popular. With the increasing popularity of mobile devices such as tablets and smartphones, the need for alternatives to the popular Flash plugin from Adobe has also been growing. Just recently, Adobe announced that Flash will no longer be supported for mobile devices. This means that Adobe itself will focus on HTML5 as a key technology for those devices - and desktop systems sooner or later.

One disadvantage of HTML was the lack of multimedia techniques. In HTML, you could not display a video or draw on the screen. With HTML5, new elements such as <video> and <canvas> have been introduced. Those elements give developers the possibility to use multimedia technology in "native" HTML, just by writing some JavaScript in combination with HTML. A basic action that should be provided by multimedia technologies is animation. In HTML5, there are some ways to create such actions.

In this article, I will only compare the new <canvas> element with the upcoming CSS3 animation technique. Other possibilities would include the creation and animation of DOM elements or SVG elements. Those possibilities will not be included in this discussion. It should be noted from the beginning that the canvas-technology is supported in the current releases of all major browsers, while CSS3 animations are only possible in the latest editions of Firefox and Chrome. The next IE will also provide CSS3 animations.

Background

I am currently giving a lecture on creating WebApplications using HTML5, CSS3 and JavaScript. This is a lecture with tutorials. For one of the tutorials, I picked a sample canvas animation - just showing in which direction we are heading to with a technology like this. Then I introduced the CSS3 animation in the lecture (everyone was very excited about it) and wanted to create a simple homework task using the CSS3 animations. What came to my mind was: How easy or hard would it be to actually transform the canvas animation into a complete CSS3 animation?

This involved several parts:

  • Creating all the different <div>-elements in order to "box" everything
  • Draw everything using styles on those elements with style rules like borders, background-gradients and rounded corners
  • Actually animating the elements

The reason for using CSS3 animation over the <canvas>-element is quite important: While browsers can optimize their elements performance (regarding their style, i.e. CSS), they cannot optimize our custom drawing routines used in a canvas. The reason for this lies in the browser's ability to use hardware mainly the graphics card. Currently the browser does not give us direct access to the graphics card, i.e., every drawing operation has to go over some function in the browser first.

This problem could be prevented with techniques such as webgl, where the browser does give the developer direct access to the graphics card. However, this is treated as a security problem and will not become standardized. One important rule for developing WebApplications is standardization - since this is our portal to a huge customer base. If we excluded some of the most popular browsers, we would certainly lose a lot of potential visitors.

Starting with Canvas

Our basic HTML document looks like the following:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Animations in HTML5 using the canvas element</title>
<script></script>
</head>
<body>
<canvas id="canvas" width="1000" height="600">
Your browser does not support the <code>&lt;canvas&gt;</code>-element. 
Please think about updating your browser!
</canvas>
<div id="controls">
<button type="button" onclick="speed(-0.1);">Slower</button>
<button type="button" onclick="play(this);">Play</button>
<button type="button" onclick="speed(+0.1);">Faster</button>
</div>
</body>
</html>

Here we set the HTML5-Doctype and build a page containing a <canvas> for drawing the animation and some buttons contained in a panel (<div>). We could have shortened the document by omitting certain tags. One of the advantages of HTML5 is that each browser has to implement certain fallbacks, e.g., if a tag is not closed or if a certain tag is missing. The shown (more complete and verbose) form is my personal favorite.

A canvas alone would not make a great painting - nor does it give us some animation. What does the actual drawing? It's some JavaScript! The required <script>-tag is already placed in the <head>-section. In this case, we will not use external JavaScript files to get the required lines of code. This only makes sense if you consider using not many lines of code (small size) and non-repeating JavaScript. Otherwise, you could benefit from the browser's caching as well as multiple (download) connections. Another important point by using external JavaScript files is to consider moving the script-tags to the bottom of the document in order to prevent performance problems.

Let's start by declaring some variables:

var    dx = 5,                // Velocity at rate 1
    rate = 1,               // The current playback rate
    ani,                       // The current animation loop
    c,                         // (Drawing) Canvas Context
    w,                         // (Car, hidden) Canvas Context
    grassHeight = 130,         // Height of the background
    carAlpha = 0,              // Rotation-angle of the tires
    carX = -400,              // (x-)Position of the car (will move)
    carY = 300,               // (y-)Position of the car (will stay constant)
    carWidth = 400,           // Width of the car
    carHeight = 130,          // Height of the car
    tiresDelta = 15,          // Distance from one tire to the closest edge of the 
                    //chassis of the car
    axisDelta = 20,       // Distance from the bottom of the chassis of the car 
                    //to the axis with the tires being attached
    radius = 60;              // Radius of one tire

    // In order to init the car canvas (hidden) we use this anonymous function
(function() {
    var car = document.createElement('canvas'); // Creating the element
    car.height = carHeight + axisDelta + radius;// Setting the appropriate attributes
    car.width = carWidth;
    w = car.getContext('2d');                   // Now we can set the car canvas
})(); // Executed directly

As you can see, most of the variables are actually used as constants. This means the code will give you some freedom in order to adjust the dimensions of the car as well as some other parameters like the speed of the car. The change in the rotation of the tire can actually be calculated by using the ratio of dx to the radius.

Another interesting property arises by looking at the bottom of the code. Here I use an anonymous function. This can be very useful in some cases. This means that variables that are declared within the scope of the anonymous function like car will be deleted by the browser and most importantly will not cause any conflicts with other existing variables.

Why is there a second (invisible) canvas set up? First of all, this will be useful in the transition to CSS3. However, this approach is also very useful in general. By using a canvas for each major object we are animating, we will be able to keep their states as they are, i.e. if we do not have to redraw them we do not have to as supposed to the approach where we will wipe does drawings away with a clean of the main canvas. Another reason is that this model allows us to think more physical. We do not have to animate the chassis (moving) and the tires (moving and rotating) separately, but just as a whole thing meaning the tires (rotating) within the car (moving). This projects the physical model more closely, where the tires are rotating which makes the car moving.

Starting the loop:

function play(s) {                  // The referenced function, s is the button
    if(ani) {                        // If ani is not NULL we have an animation
        clearInterval(ani);            // So we clear (stop) the animation
        ani = null;                    // Explicitly setting the variable to NULL
        s.innerHTML = 'Play';          // Renaming the button
    } else  {
        ani = setInterval(drawCanvas, 40); // We are starting the animation with 25 fps
        s.innerHTML = 'Pause';         // Renaming the button
    }
}

This function has already been referenced by the HTML we've written. Here we just start or stop the animation depending on the current state that is displayed using the ani variable. The framerate has a maximum of 25 frames per second - this might not be the best choice. jQuery is using 77 fps (13 ms) as a default for its DOM object animation. In case of this simple animation, it should give a good insight. An important issue is that our logic (dx = 5) will be bound to those 25 fps. This is something to be careful about when building professional animations or even games.

Let's have a look at the main drawing function that will be called in the loop:

function drawCanvas() {
    c.clearRect(0, 0, c.canvas.width, c.canvas.height);  // Clear the (displayed) canvas 
                                          // to avoid errors
    c.save();                                            // Saves the current state of 
                                           // properties (coordinates!)
    drawGrass();                                         // Draws the background (grass)
    c.translate(carX, 0);                                // Does some coordinate 
                                          // transformation
    drawCar();                                           // Draws the car (redraws 
                                                         // hidden canvas)
    c.drawImage(w.canvas, 0, carY);                      // Draws the car actually to 
                                                         // visible canvas
    c.restore();                                         // Restores to last saved state 
                                                         // (original coordinates!)
    carX += dx;                                          // Increases the position by 
                                                         // the dx we set per time
    carAlpha += dx / radius;                             // Increases the angle of the 
                                                         // tires by the ratio

    if(carX > c.canvas.width)                            // We keep some periodic 
                                                         // boundary conditions
        carX = -carWidth - 10;                           // We could also reverse the 
                                                         // speed dx *= -1;
}

Basically we just redraw the image. The animation is actually coming from two little methods inside. First we use a translation of coordinates to always draw to the same coordinates but being placed on a different location. The second one is the incrementation of the car's current position. Without one of those two calls, the car would not move at all! We also outsourced as much code as possible, making it more maintainable (unfortunately this can also decrease JavaScript code performance).

Why are coordinate transformations so important? They offer us some nice possibilities: Rotating something without writing any mathematical function at all! Or as we have seen: We can just draw to the same coordinates but receive different results. This is the power of coordinate transformation. The translation method is called by using context.translate(dx, dy) and allows us to set a new center (0, 0). The usual center is the upper left corner. If we would rotate without translating at all, we would always just rotate around the upper left corner. In order to rotate around the center of a canvas, we could use something like:

context.translate(context.canvas.width / 2, context.canvas.height / 2)

Now we look at the function for drawing the car itself:

function drawCar() {
    w.clearRect(0, 0, w.canvas.width, w.canvas.height);  // Now we have to clear 
                        //the hidden canvas
    w.strokeStyle = '#FF6600';     // We set the color for the border
    w.lineWidth = 2;               // And the width of the border (in pixels)
    w.fillStyle = '#FF9900';       // We also do set the color of the background
    w.beginPath();                 // Now we begin some drawing
    w.rect(0, 0, carWidth, carHeight);  // By sketching a rectangle
    w.stroke();                         // This should be drawn (border!)
    w.fill();                           // And filled (background!)
    w.closePath();                      // Now the close the drawing
    drawTire(tiresDelta + radius, carHeight + axisDelta); // And we draw tire #1
    drawTire(carWidth - tiresDelta - radius, carHeight + axisDelta);// Same routine, 
                        //different coordinates
}

Here we used some of the possibilities that the <canvas>-element offers us. On the hand we do use paths (very simple - and in this case more or less obsolete since we could have used the drawRect() and fillRect() method. Again we outsourced some code in order to obtain a better maintainability. In this case, this is totally justified since we just have one method to take care of (drawTire()) instead of two identical code blocks that form one big mess.

Finally, let's have a look at the method for drawing one of the tires:

function drawTire(x, y) {
    w.save();                      // Again we save the state
    w.translate(x, y);             // And we perform a translation 
                    // (middle of the tire should be centered)
    w.rotate(carAlpha);            // Now we rotate (around that center)
    w.strokeStyle = '#3300FF';     // Setting the draw color
    w.lineWidth = 1;               // The width of the border (drawline)
    w.fillStyle = '#0099FF';       // The filling / background
    w.beginPath();                 // Another sketching is started
    w.arc(0, 0, radius, 0, 2 * Math.PI, false); // With a full circle around the center
    w.fill();                      // We fill this one
    w.closePath();                 // And close the figure
    w.beginPath();                 // Start a new one
    w.moveTo(radius, 0);           // Where we move to the left center
    w.lineTo(-radius, 0);          // Sketch a line to the right center
    w.stroke();                    // Draw the line
    w.closePath();                 // Close the path
    w.beginPath();                 // Start another path
    w.moveTo(0, radius);           // Move to the top center
    w.lineTo(0, -radius);          // And sketch a line to the bottom center
    w.stroke();                    // Draw the line
    w.closePath();                 // Close it
    w.restore();                   // And restoring the initial state (coordinates, ...)
}

Additionally, I've added a preview image by using the onload-event of the <body>-element. I also included a method to increase the speed of the animation by changing the value of the dx variable. The framerate will not be changed in order to increase or decrease the speed.

This is the result of our coding. A car moving around in a canvas!

This example can be viewed live at http://html5.florian-rappl.de/ani-canvas.html.

Moving to CSS3

The basic HTML document now has the following format:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Animations in HTML5 using CSS3 animations</title>
<style>
</style>
</head>
<body>
<div id="container">
<div id="car">
<div id="chassis"></div>
<div id="backtire" class="tire"><div class="hr"></div><div class="vr"></div></div>
<div id="fronttire" class="tire"><div class="hr"></div><div class="vr"></div></div>
</div>
<div id="grass"></div>
</div>
</body>
</html>

The document looks quite similar to the one before. However, some things can be noted right away:

  • There is no <script>-element prepared. Instead, we will use a <style>-declaration.
  • There are no controls for setting the speed or play and pause. This is actually one of the key differences I will explain later (interaction).
  • The whole scene is already presented in HTML-elements. They are not styled right now, but they do represent the scenario we want to animate.

While animating something in a canvas is mostly a programming job, we clearly shifted towards a design problem here. We can only manage to animate the scene appropriately if we build a correct model of the scene using HTML-elements (well, <div>s). In this logical scenario, the <div id="container"> represents the canvas from before, <div id="car"> is the HTML-element that is equivalent to the hidden canvas of the previous example and <div id="grass"></div> is exactly the element that has been drawn using drawGrass() before.

The basic CSS outline is the following:

#container
{
    position: relative;        /* Relative pos. - just that we can place absolute 
            divs in there */
    width: 100%;            /* Yes this will get the whole page width */
    height: 600px;            /* and 600px height */
    overflow: hidden;        /* Really important */
}

#car
{
    position: absolute;        /* We position the car absolute in the container */
    width: 400px;            /* The total width */
    height: 210px;            /* The total height incl. tires and chassis */
    z-index: 1;               /* car is in front of the grass */
    top: 300px;                /* distance from the top (y-coordinate) */
    left: 50px;            /* distance to the left (x-coordinate) */
}

#chassis
{
    position: absolute;            /* This defines the space of our car w/o tires */
    width: 400px;                  /* The total width */
    height: 130px;                 /* The height of the chassis */
    background: #FF9900;           /* Some color */
    border: 2px solid #FF6600;        /* Some thick border */
}

.tire
{
    z-index: 1;                        /* Always in front of the chassis */
    position: absolute;                /* Absolute positioned */
    bottom: 0;                        /* Will be placed at the bottom of the car */
    border-radius: 60px;            /* And there is our radius ! */
    height: 120px;                   /* 2 * radius = height */
    width: 120px;                    /* 2 * radius = width */
    background: #0099FF;            /* The filling color */
    border: 1px solid #3300FF;        /* And the border color and width */
}

#fronttire
{
    right: 20px; /* Positions the right tire with some distance to the edge */
}

#backtire
{
    left: 20px; /* Positions the left tire with some distance to the edge */
}

#grass
{
    position: absolute;    /* Grass is absolute positioned in the container */
    width: 100%;        /* Takes all the width */
    height: 130px;        /* And some height */
    bottom: 0;            /* 0 distance to the bottom */
    background: -webkit-linear-gradient(bottom, #33CC00, #66FF22);
    background: -moz-linear-gradient(bottom, #33CC00, #66FF22);
    background: -ms-linear-gradient(bottom, #33CC00, #66FF22);
    background: linear-gradient(bottom, #33CC00, #66FF22); /* Currently we need 
                            all of them */
}

.hr, .vr    /* Rules for both: hr and vr */
{
    position: absolute;     /* Want to position them absolutely */
    background: #3300FF;    /* The border color */
}

.hr
{
    height: 1px;    /* Linewidth of 1 Pixel */
    width: 100%;    /* Just a straight line (horizontal) */
    left: 0;
    top: 60px;        /* Remember 60px was the radius ! */
}

.vr
{
    width: 1px;        /* Linewidth of 1 Pixel */
    height: 100%;    /* Just a straight line (vertical) */
    left: 60px;        /* Remember 60px was the radius ! */
    top: 0;
}

Here we use classes and IDs to attach rules one or multiple times. This can be seen best by looking at the code for creating a tire. We do use two different IDs (fronttire and backtire) for distinguishing between different positions. Since both share the rest of their properties (and a rotation!) it was just useful to give them also a class (tire) which attaches some CSS rules to the corresponding elements.

The horizontal and vertical lines will be drawn using the classes hr and vr. We can use the position: absolute; and position: relative; rules to set positions for each child-element within the mother-element. For the grass, we required a gradient to be drawn. This is no problem in CSS3. Well, maybe it is a small problem since the browser vendors are currently using vendor specific prefixes for those rules. This is an annoying fact and will hopefully vanish within the next year.

A really important rule can be found in the container itself. By setting overflow: hidden; we prevent the car from escaping from our beautiful container-world. This means that the car will behave the same way as in the previous scenario, where it was not possible to draw outside the canvas-area. However, one more really important thing is missing: the animations itself!

Definitions of the keyframes:

@keyframes carAnimation
{
    0% { left: -400px; }    /* Initial position */
    100% { left: 1600px; }    /* Final position */
}

@keyframes tyreAnimation
{
    0% { transform: rotate(0); }             /* Initial angle */
    100% { transform: rotate(1800deg); }    /* Final angle */
}

The first comment on those lines is a really important one: do not try making this at home! I do not show the real code here for a purpose. This is how it should look like. However, it does not (but it looks similar). The problem is that CSS3 keyframes are quite new and therefore (yes, you guess (or know) it) you require those prefixes again. Also CSS3 transforms do require prefixes. So just copy those lines and add -webkit- and -moz- and others (IE is about to come to the CSS3 animations party with version 10, hopefully Opera is about to follow!) in front of keyframes and transform.

The next thing you'll notice is that the syntax is really clean and smooth. You just specify some keyframes (in this case just one as an initial frame and one as a final frame) and the browser will calculate the rest. However, we cannot integrate such nice physical considerations like the relation between the angle and the position from the previous example.

Now that we specified some keyframes we need to integrate them! How does that look:

#car
{
    animation-name: carAnimation;
    animation-duration: 10s;
    animation-iteration-count: infinite;
    animation-timing-function: linear;
}

.tire
{
    animation-name: tyreAnimation;
    animation-duration: 10s;
    animation-iteration-count: infinite;
    animation-timing-function: linear;
}

Again - be cautious when trying this at home. You will need some copy and paste plus you'll have to add the appropriate prefixes in front of those entries. So this tells the browser which set of keyframes to take for the element and what kind of frame distribution within which time to consider. We also tell the browser the number of repetitions to perform. In this case, we will end up with an infinite loop.

This is the result of our styling. A car moving around in a div!

This example can be viewed live at http://html5.florian-rappl.de/ani-css3.html.

Another Approach: Hybrid!

Since the keyframes feature is not well supported, it is obvious that other possibilities have to be explored. One possibility is to combine the power of (available) CSS3 features such as linear gradient, transformations and transitions with the power of JavaScript. This means that we will have the best of both worlds. Even though some CSS3 features might be missing in one or the other browser, it could be possible to do quick replacements in the form of filters (possible in the Internet Explorer), images / scripts or even nested canvas elements.

The hybrid approach I am going to present right now will start off where the CSS3 keyframes example stopped. The only thing that has to be modified are all @keyframes and animation rules in the CSS stylesheet. Once these lines have been removed, we are stuck with a static image. Now we do see the environment, but nothing is happening. This is where JavaScript comes to help.

We could start with a loop as in the canvas example. However, I do want to present a different way. Since we are dealing with DOM objects, i.e. HTML elements like a <div>, we could make use of the very effective (and cross-browser friendly) library jQuery. It is not only one of the fastest libraries - it is also one of the most popular ones. So we have to include a few new lines in the head of the HTML file. The <head> element will be extended in the following way:

<!-- Rest of Head Tag (including style Tag) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script>
    //Some Script here
</script>
<!-- Finish Head here -->

Here we use jQuery as a minified version from the Google CDN. The advantage of using popular libraries from public CDN of reliable hosts is that one might need fewer requests due to caching, has less traffic on the (own) server and serves the homepage faster since the client can make more requests in parallel (the number of requests to one host is limited from the browser). Now we will write some script using jQuery's ready method. This method gives us the guarantee that the DOM has finished loading and is ready to be modified from our JavaScript.

$(document).ready(function() {
    var rot = 0;
    // Find out prefix and set it --- could be done more generally 
    // but we just need it once!
    var prefix =     $('.tire').css('-o-transform') ? '-o-transform' : (
                    $('.tire').css('-ms-transform') ? '-ms-transform' : (
                    $('.tire').css('-moz-transform') ? '-moz-transform' : (
                    $('.tire').css('-webkit-transform') ? 
            '-webkit-transform' : 'transform')));

    var origin = {    /* The original point that we start from */
        left : -400,        // from which point do we want to start? integer --> 
            // value is in pixels
    };

    var animation = {    /* The animation executed by jQuery */
        left : 1600,        // to which point do we want to move? since 
            // it is an integer it is in pixels
    };

    var rotate = function() {    /* The function to be called for rotating the tires */
        rot += 2;
        $('.tire').css(prefix, 'rotate(' + rot + 'deg)');
    };

    var options = {    /* The options to be used by jQuery */
        easing: 'linear',        // Only a linear easing makes sense here
        duration : 10000,        // 10 * 1000 ms = 10 sec.
        complete : function() {    // once the animation is done it should restart
            $('#car').css(origin).animate(animation, options);
        },
        step :    rotate,        // call a function every step to rotate tire!
    };

    options.complete();
});

So what are we doing here? First, I introduce two very useful variables. One will be used in order to determine the current status of the tires. It will save the current angle of the rotation. This angle will be incremented and used to set the rotation. The second one is a shame... Actually it is based on the statements I did make with the CSS3 keyframes. At the moment, browser vendors use their own prefixes for a lot of things - even though more and more rules are being standardized, this is required. My statements are a very easy approach to find out the actual statement that will be accepted by the browser as a CSS rule to set the rotation. Another (and more general) way would be to query the navigator object which contains the browser name and more. That information could be used to set a global prefix like -o-, -moz- etc.

The next two objects are introduced to give some kind of extensibility to the code. Both will be used in order to determine starting point and end point of the animation. These objects can be extended or modified in order to produce a different animation based on the same basic environment. The rotate function will be used in each frame and will perform the rotation on the tires using jQuery's built in css() modification function.

Now we do create the options object containing the duration, the step callback and the complete callback. The last one will be used to produce the infinite loop as in the CSS3 example. We actually do set the options variable that is just created as the options reference for the jQuery animate() function. Before we call the animate() method, we use jQuery's CSS method again. This will ensure that we animate from the starting point to the end point. Here we use jQuery's chaining to make to function calls (with the selector even three) in just one JavaScript statement.

The final step is to execute the animation using our complete callback. This hybrid approach does even work in IE 9 (except the linear gradient for the grass - I did not use a filter or other methods here to guarantee compatibility). It provides a cross-browser experience in Opera, Firefox, Safari and Chrome.

This example can be viewed live at http://html5.florian-rappl.de/ani-hybrid.html.

Bonus: How About Vector Graphics?

Yet there is another possibility that should be included in this list. This possibility is somehow out of the box, since it does not rely on HTML5 techniques like the <canvas> element or CSS3 rules like rotations. However, this is the great advantage of this technique. On the one hand, it is compatible with older browsers and on the other sides it is possible to hardware accelerate it on newer browsers. I talk about the possibility to animate SVG graphics.

SVG is another standard that has been set by the W3C. It has been around for quite a long time and is a very successful file format for seamless vector graphic exchange. Popular programs like Inkscape heavily rely on the SVG format, which is based on XML. Many browser vendors do support including SVG graphics in websites. The advantages are obvious: The graphics scale nicely and do behave like real objects. This means that a hole in graphic does infact represent a hole (cannot be clicked), and not just a bunch of pixels in a different color. With SVG, it is possible to create complex objects which can really be treated like such.

One of the first browsers to support SVG was Opera. The support had only one major drawback: SVG could just be included externally, i.e., not in the same document (HTML), but in an external *.svg file. This drawback has just now (about 3 months ago) been removed. One possible workaround was to use VML in the HTML document. VML is another description language for vectors. It was the language of choice from the Internet Explorer team. Today IE 9 also supports SVG, but former versions of IE just supported VML. How do we get around this mess (VML, SVG, ...)? We pick a JavaScript library that gives us a better cross-browser experience. Something like jQuery for SVG... That one is named RaphaelJS!

How does the basic HTML code look like?

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Animations in HTML5 using CSS3 animations</title>
<style>
body
{
    padding: 0;
    margin: 0;
}
#container
{
    position: relative;        /* Relative pos. - just that we can place absolute divs 
            in there */
    width: 100%;            /* Yes this will get the whole page width */
    height: 600px;            /* and 600px height */
    overflow: hidden;        /* Really important */
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="raphael.js"></script>
<script></script>
</head>
<body>
<div id="container"></div>
</body>
</html>

The HTML code looks a bit like one from the CSS3 keyframes or hybrid example, except we only make rules for the body and the container <div>. The container will be used for our SVG animation. We note that we include jQuery again (to use jQuery's wonderful $(document).ready() method. We also include the external JavaScript library raphael.js. This one has been downloaded from the website (raphaeljs.com/). The real work has now to be done - in JavaScript (again).

$(document).ready(function() {
    var radius = 60,       //Radius of the tyres
        tyreDist = 15,     //Distance from the edges of the chassis to the edge of a tyre
        carDist = 300,     //Distance from the top of the car to the top of the container
        carHeight = 130,   //Height of the car
        carWidth = 400,    //Width of the car
        axisDist = 20,     //Distance from the bottom of the car to the center of the tyre
        grassHeight = 130, //Height of the grass
        left = -400,       //Starting position (left side)
        diff = 2000,       //Difference from final position to starting position 
            //(right side = 1600px in this case)
        angle = 1800,      //Final angle in degrees
        time = 10000,      //Time for the animation in ms
        ease = 'linear';   //Easing mode for the animation
    
    //creates new SVG context
    var paper = Raphael('container');
    
    //creates the grass
    var grass = paper    /* accesses the svg context */
        .rect(0, paper.height - grassHeight, paper.width, grassHeight)    /* creates the 
                            grass rectangle */
        .attr('fill', '90-#33CC00-#66FF22')     /* fills the rectangle with a 
                    (linear) gradient */
        .attr('stroke-width', 0);             /* no stroking for this rectangle */
        
    //creates the chassis of the car
    var chassis = paper    /* accesses the svg context */
        .rect(left, carDist, carWidth, carHeight)        /* creates the rectangle for 
                        the chassis of the car */
        .attr('fill', '#F90')                            /* fills the rectangle */
        .attr('stroke-width', 2)               /* sets the stroke width to 2 pixels */
        .attr('stroke', '#F60');               /* and sets the stroke color */
        
    //creates the left tyre of the car
    var tyre1 = paper    /* accesses the svg context */
        .circle(left + tyreDist + radius, carDist + 
        carHeight + axisDist, radius)        /* creates a circle for the tyre */
        .attr('fill', '#09F')                         /* fills the circle */
        .attr('stroke', '#30F');                      /* and sets the stroke color */
        
    //creates the horizontal line of this tyre
    var tyre1_lh = paper      /* accesses the svg context */
        .path([               /* creates a path (will be a (horizontal) line) */
            'M',               /* move to origin of line */
            left + tyreDist,                    /* x */
            ',',
            carDist + carHeight + axisDist,        /* y */
            'L',                                /* draw line till */
            left + tyreDist + radius + radius,    /* x */
            ',',
            carDist + carHeight + axisDist        /* y */
            ].join(''))
        .attr('stroke', '#30F')
        .attr('stroke-width', 1);
        
    //creates the vertical line of this tyre
    /* repetition of the horizontal line - just vertical */
    
    //creates the right tyre of the car
    /* about the same as with the left tyre */
    
    //put all the stuff for the tyres in one array
    var tyres = paper    /* accesses the svg context */
        .set()            /* starts a new grouping */
        .push(            /* adds elements to the group */
            tyre1, tyre1_lh, tyre1_lv, tyre2, tyre2_lh, tyre2_lv
        );
    
    //save the various kinds of animation endpoints (transform-tos)
    var transformTo = [ 't' + diff + ',0', 'r' + angle ];
    
    //function for the animation loop (resets everything to origin and performs animation)
    var animationLoop = function() {
        chassis    /* accesses the chassis */
            .attr('transform', '')    /* resets the transformation */
            .animate({ transform : transformTo[0] }, 
        time, ease, animationLoop);    /* starts the animation */
        
        tyres    /* accesses the (part of the) tyre */
            .attr('transform', '')    /* resets the transformation */
            .animate({ transform : transformTo.join('') }, 
            time, ease);        /* starts the animation */
        }
    };
    
    //starts the animation
    animationLoop();
});

The code looks like a mixture of the hybrid approach with the canvas example. On the one side, we do have the same pattern; everything has to be initialized in JavaScript and not in CSS. We could write the SVG in HTML directly, however, this would take away the cross-browser advantage that Raphael is offering us. This cross-browser advantage is based on the fact that Raphael can create VML code as well - if that is the only one supported by the executing browser. Anyway: We want to stay on the save side and therefore we have to do everything in JavaScript. After initializing all the objects over the variables we introduced, we have to group them in a way and execute the animation.

As with jQuery, we have to set the callback method for finishing the animation to the animation itself. Therefore we create an infinite loop since the animation is recreated on ending. This also creates the requirement for a reset of the objects' properties. As we do see: The code as not as clean as with the hybrid approach and not as messy as with the canvas approach. After all, SVG should be your first choice if you need scalability. It would also be your first choice if you want rich mouse interaction without any hit detection happening.

This example can be viewed live at http://html5.florian-rappl.de/ani-svg.html.

Comparison of the Two Methods

In my opinion, both of those methods have their benefits. The first one is actually pretty straight forward from a programmer's perspective (however, it took quite long until this was possible in HTML / JavaScript). The second one does offer some really nice features and will most certainly be used in an amazing way to build websites that will certainly have an impact on the web's future.

Pro Canvas-Animations

  • Interaction (like pause, slower, faster, ...) is easily possible.
  • We can connect values to form one physical unit directly.
  • We do not need to know or have a complete structure before the animation.
  • We are completely free from the CSS prefix hell.

Pro CSS3-Animations

  • The performance is definitely better.
  • We can use the full screen (100% does not work with the canvas element).
  • The frames are computed by the browser - we just specify the keyframes.
  • We do not need JavaScript (could be switched off).

All in all, it will depend on the project (as usual). Even though I thought about some possible workarounds to actually stop an infinite CSS3-animation (and start again), it would be tedious to actually implement this in a bigger multimedia animation. Here, the interaction possibilities are a clear indicator for a canvas-animation. On the other side, some small (non-interactive) animations could be done by the CSS3 technique. It provides a nice clear syntax that can be GPU accelerated by modern browsers and can be styled to fit everywhere.

Update: Pro Hybrid-Approaches

  • The performance is better than on canvas.
  • We can use the full screen (100% does not work with the canvas element).
  • The frames are computed by us (or jQuery in this case).
  • We can provide rich interactions with JavaScript and can still show the basic image without JavaScript.

I really do like the hybrid approach since it gives a lot of flexibility with a lot of comfort. It is one way to work around the CSS3 prefix hell that is going on right now - however, this argument just applies to certain situations and might totally crash in others. Another possible hybrid approach would be to build the separate parts with separate <canvas> elements and perform JavaScript (with or without jQuery) or even keyframe animations on them.

Update: Pro SVG-Animations

  • The performance is better than on canvas.
  • Rich mouse interaction with the elements is possible.
  • The elements are scaling very nicely.
  • It is the approach that most browsers will understand.

I like SVG, but that goes hand in hand with RaphaelJS. I tried to write SVG code by hand - several times. Every element has special attributes, every attribute has its own syntax and there are so many elements. Also you have to consider special namespaces and so on. There is a lot of mess going on and this makes a good library necessary. Since there might be some problems with some browsers a good approach contains VML code as well. Next you want to have the power of chaining (or better: the same approach as in jQuery) plus animations (also jQuery like) and lots of other features. This is all offered by RaphaelJS. I would just make one statement to SVG: I Don't Always Use SVG, but when I do I Use RaphaelJS.

Points of Interest

While writing a lot of <canvas>-animations, I never had the opportunity to write an actual CSS3 animation. This was certainly related to the propagation of this really new technique. Now that Firefox implemented the keyframes and with Microsoft up to come we will most certainly see more keyframe rules online. I thought that this technique was quite hard to write and got really surprised how easy it is. I actually had more problems with the linear gradient syntax than the keyframes one.

My only hope is that browser vendors will stop rolling out CSS rules with those prefixes. In nightly builds or such they are acceptable, but having something in a final build that is either not the standard (why rolling it out?) or standardized (why prefixing it?) but with a prefix is just a mess for web developers.

History

  • v1.0.0 | Initial Release | 09.12.2011
  • v1.1.0 | Update (Some typos fixed, included hybrid approach) | 11.01.2012
  • v1.2.0 | Update (Included SVG approach) | 22.01.2012

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Florian Rappl
Chief Technology Officer
Germany Germany
Florian is from Regensburg, Germany. He started his programming career with Perl. After programming C/C++ for some years he discovered his favorite programming language C#. He did work at Siemens as a programmer until he decided to study Physics. During his studies he worked as an IT consultant for various companies.
 
Florian is also giving lectures in C#, HTML5 with CSS3 and JavaScript, and other topics. Having graduated from University with a Master's degree in theoretical physics he is currently busy doing his PhD in the field of High Performance Computing.
Follow on   Google+

Comments and Discussions

 
GeneralMy Vote of 5 PinprofessionalAnoop Kr Sharma4-Aug-13 16:57 
GeneralRe: My Vote of 5 PinmvpFlorian Rappl4-Aug-13 22:51 
QuestionThanks Pinmembervivek51129-Jan-13 23:22 
GeneralMy vote Pinmemberonurag1918-May-12 1:48 
GeneralRe: My vote PinmemberFlorian Rappl18-May-12 3:35 
GeneralMy vote of 5 Pinmemberonurag1918-May-12 1:48 
GeneralMy vote of 5 Pinmembermanoj kumar choubey27-Feb-12 3:33 
QuestionNice work PinmemberDean Oliver25-Jan-12 5:08 
GeneralMy vote of 5 PinmemberHernan Saez23-Jan-12 9:19 
GeneralMy vote of 5 Pinmvpthatraja22-Jan-12 21:43 
GeneralMy vote of 5 Pinmembersam.hill22-Jan-12 12:58 
GeneralMy vote of 5 PinmemberAkram El Assas22-Jan-12 11:58 
GeneralMy vote of 5 PinmemberJose David Pujo17-Jan-12 0:07 
GeneralMy vote of 5 PinmemberMihai MOGA13-Jan-12 19:53 
QuestionMy vote of 5 PinmemberGandalf - The White12-Jan-12 22:16 
GeneralMy vote of 5 Pinmembermauriciobarros12-Jan-12 6:53 
GeneralMy vote of 5 PinmemberPeteBarber11-Jan-12 23:49 
GeneralRe: My vote of 5 PinmemberFlorian Rappl12-Jan-12 0:14 
GeneralMy vote of 5 PinmemberSmithers-Jones11-Jan-12 22:25 
GeneralMy vote of 5 PinmemberPablo Aliskevicius11-Jan-12 21:06 
GeneralMy vote of 5 PinmemberMacGinitie11-Jan-12 9:38 
GeneralMy vote of 5 PinmemberPrasad J22-Dec-11 18:26 
Questionmy 5 PinmemberShahriar Iqbal Chowdhury21-Dec-11 9:54 
GeneralMy vote of 5 PinmemberAbinash Bishoyi20-Dec-11 23:49 
GeneralMy Vote of 5 PinmemberRaviRanjankr14-Dec-11 22:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140926.1 | Last Updated 22 Jan 2012
Article Copyright 2011 by Florian Rappl
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid