January 23, 2002
I'm pleased to welcome Antoine Quint as an XML.com columnist. Antoine will be writing monthly on SVG, bringing his distinctive French style to this exciting new technology.
In order to view the SVG examples you will need Adobe's SVG viewer installed -- ED.
The Web has been constantly evolving since it appeared about a decade ago. The days of plain text web sites are largely gone now and, among other things, vector graphics have become an increasingly common part of web pages. SWF, better known as the format used by Macromedia Flash, has become the medium of choice for the publication of high-octane all-singing, all-dancing presentations. However, SWF has its limitations, dynamic publishing being a big one. If you are reading this column, then you have probably already heard a little bit about the emerging XML vector graphics format called SVG. Today we will start our foray into the neat world of SVG animation.
Animation is a core feature of SVG. It is a large part of SVG's specification and is based on SMIL Animation. In fact, if you know about SMIL animation already then this article ought to be a doodle or a nice little bit of review. You might want to have the SVG Animation spec chapter handy before we start. My mission in this article is to show you how to recreate one of those nifty gravity animation effects that people gaze at for hours. SVG will certainly not make this kind of animation any more useful than its implementation in Flash, but it is certainly very instructive to create. For a little taste of what we're going to create, have a look at Niklas Gustavsson's original SWF animation, although we're going to add a few enhancements. Grab your favorite text editor and we will be off!
Creating the Graphics
Niklas was kind enough to provide the actual source to his animation on his site, which is going to help us a lot in creating a similar animation in SVG. I downloaded the source, opened it in Macromedia Flash 5, selected one of the little cubes, and copied it onto the clipboard. Then I picked up my favorite SVG-enabled tool, Adobe Illustrator 10, created a new document and pasted the cube right in. This is a good point to start working with SVG, so I asked Illustrator to save the document as SVG. Here's what we've got to work with
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 665 250" xml:space="preserve"> <g id="cube" style="stroke: #000000; stroke-width: 0.5; stroke-linejoin: bevel"> <path style="fill: #333333;" d="M0.112,26.271l25.032, 12.485V25.164L0.112,12.68V26.271z"/> <path style="fill: #666666" d="M25.144,38.756l25.033- 12.485H50.12V12.708L25.144,25.164V38.756z"/> <path style="fill: #cccccc" d="M50.12,12.708l0.057- 0.028L25.144,0.224L0.112,12.68l25.032, 12.484L50.12,12.708z"/> </g> </svg>
Now what we're going to do is to take this little cube's group (enclosed in the
<g> element) and transform it into an SVG symbol that we will be able
to re-use as we please. This is quite simple really, just switch the group to a symbol
add it as a definition. We then place the cube to the center of the composition and
background gray rectangle so it looks a little more like Niklas's layout. Also, we
composition a title. Now our code looks like what appears just below. From now on
take this as a given and never print the doctype and definitions:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 665 250" xml:space="preserve"> <title>SVG Animation - Cube Demonstration</title> <defs> <symbol id="cube" style="stroke: #000000; stroke-width: 0.5; stroke-linejoin: bevel"> <path style="fill: #333333;" d="M0.112,26.271l25.032, 12.485V25.164L0.112,12.68V26.271z"/> <path style="fill: #666666" d="M25.144,38.756l25.033- 12.485H50.12V12.708L25.144,25.164V38.756z"/> <path style="fill: #cccccc" d="M50.12,12.708l0.057- 0.028L25.144,0.224L0.112,12.68l25.032, 12.484L50.12,12.708z"/> </symbol> </defs> <rect width="100%" height="100%" style="fill: #999999; stroke: none;" /> <use xlink:href="#cube" transform="translate(307.5, 105.75)" /> </svg>
Starting Up Gently (And Linearly)
Now that we have the graphics ready, it is time to start with a little animation. I must warn you though: however hard you thought it would be, it will be easier! The first thing I want to show you is how to make a linear animation of the cube going up and down, up and down (and so on). We placed our symbol in the graphic using the "transform" attribute, which, although moving the symbol in the image, left the symbol's "y" coordinate attribute at its default value of "0". So if we want the animation of our object to be an offset animation, we will just change the "y" attribute for the offset and leave the "transform" alone for the original positioning:
<use xlink:href="#cube" transform="translate(307.5, 105.75)"> <animate attributeName="y" dur="2s" values="0; -45; 0; 16; 0; -7; 0; 3; 0; -2; 0; 1; 0" /> </use>
This is a start. What we've done here is to add an "animation" element as a child to our "use" element and set a few of its attributes which dictate its behavior. The "attributeName" attribute allows us to target which of our "use" element's attributes is going to be animated. This highlights one of the main features of SVG animation: it is property-based. This means that each of an SVG element's properties (that is, XML attributes or CSS properties) have their own, unique and independent animation environment by default. This allows for several neat things, the main one being that an element can have properties animated with different loop durations, loop count or whatever. This helps a great deal when trying to create random-looking declarative animation. If you're familiar with the Flash tool, you'll know that you need movie clips to achieve that kind of behavior, which is fairly advanced. SVG animation keeps it simple. Also, SVG animation allows us to actually create relationships for the synchronization of different animations so that time independence isn't the only way forward (more on that later).
Going back to the animation we just wrote, note that we also specified the duration of the animation. The "dur" attribute sets the duration, and here we specified that our animation lasts "2s", two seconds. This is another highlight of SVG animation: it is time-based. Once again, if you have any experience with Flash, you will have noticed that it is a frame-based tool, the timeline being graded in frame units. This means that one has to set up a particular frame rate when publishing SWF animation.
Adobe LiveMotion (another SWF tool) offers a time-based timeline, but it still has to generate frame-based animation when exporting SWF. I believe being time-based is an SVG advantage, as it allows us to define animations evolving in a realistic unit system (time) and leaves the actual frame-rate decision to the SVG player that will probably try to make most of your CPU to render nice and smooth animations. As we go through building this example, you may realize that SVG's animation rendering model is quite different from SWF's. An SWF authoring tool will have all the frames computed at export time while SVG animation leaves much of the computation work to the SVG player. This often leads to smaller file sizes and higher computation needs from the device. Then again, animation frame computation may not be the most demanding phase of animation, as actual drawing is probably the most demanding.
But I digress. We've almost forgotten about this semicolon-separated list of mysterious values in the "values" attribute. Well, this is quite simply the list of values that will be assigned to our "attributeName", much like keyframes in Flash. By default, an SVG player will compute a linear interpolation between each value, thereby creating the animation. It will also have those value changes happen one after the other at a regular interval equal to the duration of the animation divided by the amount of values in the list minus one.
Getting Our Mouse To Work
So far the work we've done, while valuable, is still a little lame. Niklas had his cubes reacting to mouse events generated when the user rolled over them. Let's get that to work, too. SVG animation provides a mechanism for reacting to mouse events. You might have noticed that in our previous animation we did not specify when our animation started. There is an attribute "begin" that would do exactly that, but it has a default value of "0s", which means our animation starts when the document is loaded. Here we want our animation to start when we mouse over the cube. Let's try introducing the "begin" attribute to our animation and set it to "mouseover", and see what it does:
<use xlink:href="#cube" transform="translate(307.5, 105.75)"> <animate attributeName="y" begin="mouseover" dur="2s" values="0; -45; 0; 16; 0; -7; 0; 3; 0; -2; 0; 1; 0" /> </use>
If you tried rolling your mouse over the cube you will have noticed it did start the animation. You might also have noticed that there is a bit of a strange behavior going on. If you don't move your pointer away from where you entered the cube and started the animation, then the animation will restart from the beginning when the cube falls down and goes under your pointer again. This is not what we want; we would rather have the cube not take any mouse event until the animation is finished. Once again, SVG animation provides the exact mechanism we are looking for: the "restart" attribute. By default its value is set to "always", which means that the animation is restarted at every time the situation indicated by the animation "begin" attribute is true. Another possible value is "whenNotActive", which according to the spec means that "the animation can only be restarted when it is not active [and that any] attempts to restart the animation during its active duration are ignored". This sounds like what we have been looking for, let's see how it goes:
<use xlink:href="#cube" transform="translate(307.5, 105.75)"> <animate attributeName="y" begin="mouseover" restart="whenNotActive" dur="2s" values="0; -45; 0; 16; 0; -7; 0; 3; 0; -2; 0; 1; 0" /> </use>
This is exactly the behavior we wanted. Excellent. However, we still have some way to go to a refined animation like the original. Let's move on to better things.
Upping the Pace
The key ingredient to make our animation as spiffy as Niklas's is to give the animation a acceleration/deceleration, gravity-like feel. When I opened the original source file in Flash, I noticed Niklas did two things. The first thing was adjusting the keyframes so that the animation would have different intervals between two sets of values. The other was to have easing-in and easing-out on certain intervals of the animation. Let's see how to implement that in SVG: keep your eyes peeled as this is really is the hottest part of what SVG animation offers.
Our first task is to get the values to be taken into account at non-identical intervals in time. It is time to introduce a new attribute to our growing set of friends: the "keyTimes" attribute. This attribute will help us recreate the timing that was going on in the original SWF animation; it will help us create a mapping between the "values" attribute and the time at which each value will be reached in our animation. The SVG specification says that "each time value in the keyTimes list is specified as a floating point value between 0 and 1 (inclusive), representing a proportional offset into the simple duration of the animation element". Albeit in formal terms, this is what we are after. Let's give it a shot:
<use xlink:href="#cube" transform="translate(307.5, 105.75)"> <animate attributeName="y" begin="mouseover" restart="whenNotActive" dur="2s" values="0; -45; 0; 16; 0; -7; 0; 3; 0; -2; 0; 1; 0" keyTimes="0; 0.2564; 0.5128; 0.6154; 0.6923; 0.7436; 0.7949; 0.8462; 0.8974; 0.9231; 0.9487; 0.9744; 1" /> </use>
Pay no attention to the weird values we set in the "keyTimes" attribute. These were computed by hand from the original Flash animation. It looks kind of odd because Niklas's keyframes were placed themselves at odd times. This is of course a job best suited for an SVG-enabled animation authoring tool, but here we're trying to figure out how SVG animation works exactly so it is worth doing by hand. The result we have now is somewhat paced animation. It is not as refined as Niklas's yet, but the end bit looks exactly like it ought to. As for the start of the animation, this is where Niklas had the easing-in and easing-out take place. Now watch out, because this is getting really smooth.
In Macromedia Flash 5, one can specify easing-in and easing-out between two keyframes with a percentage ranging from "-100%" to "100%". Although this is quite a jolly nice control over acceleration, SVG provides a much sweeter mechanism. Instead of having a simple percentage, SVG allows us to describe the speed behavior of an animation interval with a two-point Bezier curve (you've probably heard a lot about those if you've got any experience with SVG). What does this mean?
Draw a Bezier spline that fits in a square, starting bottom-left going top-right. The rule to draw that kind of curve is that it has to be bijective (a unique mapping of x and y). You can find quite a few of these curves just above section 19.2.8 of the spec, which are really helpful in visualizing how keySplines work. Now the x-axis represents time and the y-axis represents the percentage of the variation between the two values. That allows us to have very fine control of animation speed. While Flash only allowed us to specify constant speed variations, SVG enables multiple speed variations in one given interval. Now that we have a better idea of how keySplines work, let's put our knowledge to work:
<use xlink:href="#cube" transform="translate(307.5, 105.75)"> <animate attributeName="y" begin="mouseover" restart="whenNotActive" dur="2s" calcMode="spline" keySplines="0 .75 .5 1; .5 0 1 .25; 0 .25 .25 1; .5 0 1 .5; 0 0 1 1; 0 0 1 1; 0 0 1 1; 0 0 1 1; 0 0 1 1; 0 0 1 1; 0 0 1 1; 0 0 1 1" values="0; -45; 0; 16; 0; -7; 0; 3; 0; -2; 0; 1; 0" keyTimes="0; 0.2564; 0.5128; 0.6154; 0.6923; 0.7436; 0.7949; 0.8462; 0.8974; 0.9231; 0.9487; 0.9744; 1" /> </use>
Now this looks nice, much like the real thing, in fact a lot smoother since we did not have to worry about the cumbersome frame-rate. Besides the "keySplines" attribute we just introduced, you will have noticed a new friend: "calcMode". This attribute enables tweaking of the way the interpolation between the animation values we set is done. So far, we have use its default value ("linear") and now we needed to set it to "spline" so that it would take into account our "keySplines" attribute.
Go Forth And Multiply!
One of the last things missing is actually having the sixteen instances of our cube. Since we defined our cube as an SVG "symbol", we only have to place sixteen "use" elements, much like we have used so far with our single cube. I'm not going to teach you how to line cubes up, I actually got Illustrator to give me the bounding box of the cube and did a rough alignment by hand (you can see it's not perfectly aligned when you zoom in).
In much the same way as we created a symbol for the graphics in order to create a single definition for multiple re-use, we may well want to have references to our animation since all the cubes are animated in the same way. We could have had our "animation" element as part of the symbol, but the SVG 1.0 specification states that this would result in all instances of the symbol being animated at the same time. However, animation referencing is an issue being addressed by the W3C SVG Working Group for future enhancements, and SVG 1.1 (currently in Working Draft status) introduces a mechanism allowing for animation of animated symbol instances only. Still, there is a way to actually re-create this kind of behavior, using entities. The idea is to have all the sensible values of our animation, which may be changed in our authoring, be centralized so that a single change would be applied to all the animations. Here goes:
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" [ <!ENTITY dur "2s"> <!ENTITY values "0; -45; 0; 16; 0; -7; 0; 3; 0; -2; 0; 1; 0"> <!ENTITY keyTimes "0; 0.2564; 0.5128; 0.6154; 0.6923; 0.7436; 0.7949; 0.8462; 0.8974; 0.9231; 0.9487; 0.9744; 1"> <!ENTITY keySplines "0 .75 .5 1; .5 0 1 .25; 0 .25 .25 1; .5 0 1 .5; 0 0 1 1; 0 0 1 1; 0 0 1 1; 0 0 1 1; 0 0 1 1; 0 0 1 1; 0 0 1 1; 0 0 1 1"> <!ENTITY calcMode "spline"> <!ENTITY begin "mouseover"> ]> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 665 250" xml:space="preserve"> <!-- <title>, <defs> and background <rect> as we have seen --> <g id="cubes" transform="translate(300.25, 143.45)" > <use xlink:href="#cube" transform="translate(0,-72)"> <animate dur="&dur;" values="&values;" keyTimes="&keyTimes;" keySplines="&keySplines;" attributeName="y" begin="&begin;" restart="whenNotActive" calcMode="&calcMode;" /> </use> <!-- fifteen other <use> --> </g> </svg>
Et voilà!. This might well look like a dirty trick, but it does the job quite nicely. Now if we decide to change the keySpline for the third interval of our animation, we only have to edit it once. As for the animation itself, it looks like we're done. It should look very much like the original SWF version. In fact, Adobe's SVG Viewer on my laptop gives me a much smoother animation than the SWF played in the Flash player. By the way, the gzipped version (commonly known as SVGZ) of the final animation is only 891 bytes, while the SWF version is 1.29 KB. Prettier and smaller!
Wrapping It All Up
I hope you enjoyed this ride through some of the nicest features of SVG animation. There is a lot more to SVG animation, especially the idea of synchronization, which we'll consider in future articles. I think this simple animation highlights some of the key concepts of SVG animation, primarily that it is time-based and property-based, as well as some of its differences from the SWF animation capabilities.