Thursday, September 27, 2012

A Comparision of HTML5 Animations with SVG and CSS3

A big part of building a site can be spent coding animations.  Various Javascript libraries, like jQuery.animate(), have greatly simplified this process.  However, if you can do more with less code, why not do it.  That's what I was interested in determining with both the SVG animate tag and CSS3 transition style.  The question is how well its supported, what kind of control do you have over it, and most importantly, does it reduce the amount of code you need to write.

I decided to create a very simple progress bar implemented using jQuery.animate(), CSS3 Transitions, and SVG animate.  By far, the easiest to build was the SVG version.  If I didn't want to start/stop or know when it repeated, then no code would be required to execute the animation.  The following markup is sufficient:


<svg width="300" height="100">
<rect fill="red" height="50" width="50" y="25" x="5">
<animate id="svg-a1" attributeName="x" repeatCount="indefinite" begin="indefinite" values="5;53;101;149;197;245" restart="always" calcMode="discrete" dur="1500ms" fill="freeze"></animate>
</rect>
<rect stroke-width="3" stroke="dimgray" fill="none" height="50" width="290" y="25" x="5">
</rect>
</svg>


The animate tag provides the necessary description to move the progress indicator from left to right inside the container. If you wanted it to start immediately upon loading, you would remove the begin="indefinite" attribute and the animation will loop indefinitely.  With that attribute set, we're indicating that we will be triggering the start of the animation from code:


var svgcnt = 0;

$('#svg-a1').on('repeatEvent', function ()
{
$('#svg-repeat').html(++svgcnt);
});

$('#svg-start').on('click', function ()
{
$('#svg-a1')[0].beginElement();
});

$('#svg-stop').on('click', function ()
{
$('#svg-a1')[0].endElement();
});


The animate tag exposes begineElement() and endElement() methods to the DOM which allow you to start and stop the animation. I also hooked into the repeatEvent to maintain a loop counter. After testing in all the current browsers (other than IE), I found that Chrome does not support the event. Other than this one issue, this progress bar worked really well.

Moving on to the jQuery and CSS3 versions, the following markup is shared by both implementations:

<div class="jq-container">
<div class="jq-outer">
<div id="jq-progress" class="jq-inner"></div>
</div>
</div>


The jQuery version requires some extra code to create the step effect and loop the animation:


var jqcnt = 0;

var doAnimate = function()
{
$('#jq-progress')
.animate({tabIndex: 0},
{
duration: 1500,
easing: 'linear',
step: function (now, fx)
{
fx.elem.style.left = (47 * Math.floor(6.0 * fx.pos))+'px';
},
complete: function ()
{
$('#jq-repeat').html(++jqcnt);
doAnimate();
}
});
};

$('#jq-start').on('click', function ()
{
doAnimate();
});

$('#jq-stop').on('click', function ()
{
$('#jq-progress').stop(true);
});


Here, I used the step callback to do the math to create stepping effect of the progress bar and then the complete callback to update the loop counter and restart the animation.

Sadly, the CSS3 implementation was the most difficult. I used jQuery to set the CSS styles so it would use the correct browser flavor (ie -webkit, -o, -moz):


var c3cnt = 0;

$('#c3-progress').on('transitionend webkitTransitionEnd otransitionend', function ()
{
$('#c3-repeat').html(++c3cnt);

$('#c3-progress').css(
{
transform: '',
transition: ''
});

setTimeout(function() {
$('#c3-progress').css(
{
transform: 'translate(235px,0px)',
transition: '1200ms steps(5, start) 300ms'
});
}, 1);
});

$('#c3-start').on('click', function ()
{
$('#c3-progress').css(
{
transform: 'translate(235px,0px)',
transition: '1200ms steps(5, start) 300ms'
});
});

$('#c3-stop').on('click', function ()
{
$('#c3-progress').css(
{
transform: 'translate(0px,0px)',
transition: ''
});
});


I had to bind all the events to catch each browser specific event type - jQuery did not do that for me. The case of the event name is important and inconsistent between browsers, however, once I figured it out, all the browsers supported the event callback (unlike the SVG version). Additionally, I could not reset the animation and restart it without using setTimeout to delay it it by a millisecond. The other tweak which was slightly annoying was the necessity for the 300ms delay on the animation. Otherwise, the initial position would just jump and not match the other versions. The steps() function in the transition provides less control over incrementing of the transition over time compared to the SVG animate syntax.

One of my motivation of doing this test was to see if any of the animations would continue even if Javascript code was executing. The jQuery animation would not be able to continue, but since part of the CSS3 animation (other than looping it) and the SVG animation are all being handled by the browser so I thought maybe they'd keep running. I added a intensive loop to my demo to see what happened. All the browsers except Opera stopped all the animations. Opera continued to animate the SVG and the CSS3 transition (until it ended and needed to be looped via Javascript). I was quite disappointed to find that the animations are not being handled by the browser in a separate thread and therefore not affected by the Javascript execution. I understand there are interaction points available between Javascript and the DOM objects so thread synchronization is an issue but there seems to be an opportunity to provide significant performance increases to animations via the CSS3 and SVG features.

One last point to note is the CPU utilization across browsers. On my computer (which is quite old), Firefox ran just under 10% followed by Chrome around 7%, and Opera being the least intensive at 5%. I've tried to run some fairly complex SVG animations in Firefox recently and they have just crawled. This further illustrates that there is considerable room for optimizations.

Overall, the most powerful animation that does not require any code is SVG. There is a lot of flexibility available in the markup syntax. However, I do know there are also some inconsistencies between browsers. I did not find any with the animation I did except for the issue with the event triggering in Chrome. The CSS3 variation could be achieved with stylesheets which would reduce the code, but you need to duplicate the declarations several times to cover all the browser implementations. The available control is more limited than SVG, but basic transitions could be accomplished with very little code other than changing classes on an element. In the end, there are now quite a few alternatives to creating animation effects in the browser. As support becomes more consistent, it will be interesting to see what kinds of solutions are possible - both in performance and ease of implementation.