Animating Your SVG

By Charles McCathieNevile

SVG provides a simple way of doing animation. A couple of tools let you do it in a graphic environment, but here we're going to have a look under the hood at the source code. Ideally, you should be able to understand basic SVG code, since this article is not an introduction to it. But if you're familiar with HTML and CSS you should easily be able to pick it up as we go along.

We'll start with a simple shape - a square (a rectangle with the same width and height).

<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" width="4em" height="4em" viewBox="0 0 100 100">
  <rect width="50" height="50" />
</svg>

It's just a black square. (This is an SVG image that is 4x4 em units, with an internal coordinate system defined as going from 0,0 at the top left corner, to 100,100 at the bottom right. It has one rectangle, 50x50, whose top left corner is at the default position of 0,0 and which has SVG's default style - basic black.)

Basic animations...

We can have some simple fun by animating it. Let's just make it look a bit wider (To see this happening, open the example ex-r01.svg - this is animation and it only lasts 10 seconds. You have to reload it to see it happen again):

<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" width="4em" height="4em" viewBox="0 0 100 100">
  <rect width="50" height="50">
    <animate attributeName="width" to="100" dur="10s" />
  </rect>
</svg>

We have done two things:

  1. Make the rect element into a pair of tags, so we can put something inside them
  2. Put an animate element inside it, that says to change the attribute width to 100, over the course of 10 seconds

And so, over 10 seconds, the width of the rectangle appears wider. It isn't really - if you look at the document source code, or its DOM, at any time, the width of the rect is 50. But in the presentation, the appearance of the document changes. In fact it changes smoothly over the time specified by the dur attribute of the animation, from where it was to the value specified in the to attribute. When the animation is over, it snaps back to its real value.

Let's play with the animation a bit. We can use another attribute, fill, to say what happens at the end of it. Instead of snapping back to where it started, we can tell the effect to remain in place. We can also use repeatCount to make an animation repeat itself. Let's see this with two rectangles, animating different attributes in each one. Have a look at example ex-r02.svg and what it does...

<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" width="4em" height="4em" viewBox="0 0 100 100">
  <rect width="50" height="50">
    <animate attributeName="width" to="100" dur="30s" fill="freeze" />
  </rect>
  <rect width="50" height="50" x="50" y="50">
    <animate attributeName="x" to="0" dur="3s" repeatCount="10" />
  </rect>
</svg>

Here we have added some attributes to play with, and made some different effects. We can make things repeat a number of times, or for some amount of time (with the attribute repeatDur), or even forever by using repeatCount="indefinite".

How does the animation element know which attribute to affect? There are two width attributes in our SVG, but only one of them is changed. The simple default rule is that an animation applies to its parent element - the one it is directly inside. Later on we'll see how you can override this, and have the animation affect particular identified elements.

Adding some style

We have so far let everything follow SVG's fundamental style rule - basic black. Let's have a bit of fun now with SVG's ability to style objects. We'll start with a couple of coloured squares, and see what we can do to them (in example ex-r03.svg):

<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" width="4em" height="4em" viewBox="0 0 100 100">
  <rect width="50" height="50" fill="red" opacity="0">
    <animate attributeName="opacity" to="1" dur="30s" fill="freeze" />
  </rect>
  <rect width="50" height="50" x="50" y="50" fill="blue" stroke="red" stroke-width="1">
    <animate attributeName="stroke-width" to="7" dur="3s" repeatCount="10" />
  </rect>
</svg>

We can also animate things that have a default value. We can apply two (or more) different animations at once. And, as example ex-r04.svg shows, we don't have to simply start all animations as soon as the picture appears:

<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" width="4em" height="4em" viewBox="0 0 100 100">
  <rect width="50" height="50" fill="red" opacity="0">
    <animate attributeName="opacity" to="1" dur="30s" fill="freeze" />
    <animate attributeName="x" to="50" dur="15s" repeatCount="2" />
  </rect>
  <rect width="50" height="50" x="50" y="50" fill="blue" stroke="red" stroke-width="1">
    <animate attributeName="stroke-width" to="7" dur="3s" repeatCount="10" />
    <animate attributeName="opacity" to=".1" begin="10s" dur="10s" />
  </rect>
</svg>

Taking stock (1)

So let's review what we have seen:

We can use the animate element to animate some attribute, such as

  • width and height
  • x, y, (and yes, rx and ry for rounded rectangle corners, cx, cy and r for circles)
  • opacity, and by extension stroke-opacity and fill-opacity
  • stroke-width

We can also make the animation do a few different things. We can tell it when to start, how long go go for, or when to end with the begin, dur and end attributes. We can tell it to repeat with repeatCount or repeatDur (for example repeatDur="40s" will repeat for 40 seconds). We can animate various different attributes of an element, using attributeName - including some that are not explicitly set, but have a default value such as 0 or 1 (e.g. opacity).

And we can tell an animation to maintain its final effect after it has finished, with fill="freeze", or equally, not to with fill="remove" (which is the default value, if we say nothing at all).

Wait a minute, we have seen two fill attributes used, in different ways! How does this work?

There is a normal fill attribute for the animate element. It is an XML attribute which describes what to do when an animation has finished. Its default value is none

There is also a property called fill, which can be expressed either as an attribute in XML, or as a CSS property in a style attribute or even an external CSS stylesheet. It describes the colour (or pattern) that is used to fill a painted SVG element, and its default value is black.

(If this bit seems complicated, don't worry too much. You can do a lot without worrying about it - but if you want to make complex animated SVG you will probably want to understand).

Trickier transformations...

Now let's have some fun. We're going to start with a small triangle. (Very small. A right angle triangle one pixel along the top and the left sides, so about 1.4 along the hypoteneuse).

<?xml version="1.1"?>
<svg xmlns="http://www.w3.org/2000/svg" width="10em" height="10em" viewBox="0 0 100 100">
 <path style="fill:#00f; fill-opacity:.5" d="M 0,0 L 0,1 0,1 1,0 1,0 z" />
</svg>

Example ex-b00.svg looks a little like this: It's very very small... (the border is added so you can see where it is. Unless you zoom in a lot, it doesn't look like much, really. Good thing SVG is scalable. Even with zoom at 500%, the triangle is still only 5 pixels along the top and left side...)

The astute will already notice that we have defined a path that has five points - but two pairs are zero-legth lines that go nowhere. We're going to come back and play with this a bit later. It's also blue, and semi-transparent.

The path element can have content, including animations, which is what we are going to give it. For a start, let's see it grow for us. We're going to do this by animating an attribute that we could have put there already - transform - to scale it up a bit. We can do this because transform is implied, in other words there is a default value defined in the specification so it is as if the attribute exists already. But transform can have a series of different values, not just a number, so we are going to use the animateTransform element to do the work, and tell it what kind of a transformation we want to be animating. Example ex-b01.svg shows what this looks like:

<?xml version="1.1"?>
<svg xmlns="http://www.w3.org/2000/svg" width="10em" height="10em" viewBox="0 0 100 100">
  <path style="fill:#00f;fill-opacity:.5" d="M 0,0 L 0,1 0,1 1,0 L 1,0 z">
    <animateTransform attributeName="transform" attributeType="XML"
      type="scale" from="1" to="500" dur="10s" fill="freeze"/>
  </path>
</svg>

Let's look carefully at the animateTransform again, and the attributes we are using:

attributeName
This is the attribute whose apparent value we are going to change. Most attributes can be animated in SVG - a full list is part of the specification.
attributeType
This can either be XML, or CSS. The default value for this is auto, which means that it first looks for a style property (remember the discussion about fill, above?) and if it doesn't find one looks for a normal XML attribute.
type
This is here because we are animating a transformation - one of the special features of SVG. We could instead have used the other transformation types rotate, translate, skewX or skewY, but here we want to make our little triangle bigger, so we change its scale.
from
This is the start value. We could leave it out, and let it start "where it was" as we have done so far.
to
This goes with from - it is the value we want to end up with. In this case we have made our triangle appear 500 times bigger.
dur
The is the duration. We have used 10 seconds as the time that the animation takes. (By default, animation is smooth, spaced over the duration of the animation).
fill
This says that when the animation has finished, its effect continues to be shown. The alternative value of remove is the default, so we rarely need to specify it.

Some complex changes can be done with a simple animate. Remember that our triangle is defined as a path, but that there are actually 5 points. Although triangles only have 3, as we all know, the path looks like a triangle because 2 sets of points are doubled up. Squares have 4 points, and animation is smooth. We are going to take advantage of this to move both duplicate points to a new spot, on top of each other. This will look like the triangle growing out to fill a square, as example ex-b02.svg shows:

<?xml version="1.1"?>
<svg xmlns="http://www.w3.org/2000/svg" width="10em" height="10em" viewBox="0 0 100 100">
  <path style="fill:#00f;fill-opacity:.5" d="M 0,0 L 0,1 0,1 1,0 L 1,0 z">
    <animateTransform attributeName="transform" attributeType="XML"
      type="scale" from="1" to="100"  begin="2s" dur="10s" fill="freeze"/>
    <animate attributeName="d" attributeType="XML" to="M 0,0 L 0,1 L 1,1 1,1 L 1,0 z"
      begin="5s" dur="9s" fill="freeze" />
  </path>
</svg>

We are about move to some (minimally) practical application of all this. But let's look first at one more type of animation. In example ex-b03.svg we are going to change the colour, using animateColor.

<?xml version="1.1"?>
<svg xmlns="http://www.w3.org/2000/svg" width="10em" height="10em" viewBox="0 0 100 100">
  <path style="fill:#00f;fill-opacity:.5" d="M 0,0 L 0,1 0,1 1,0 L 1,0 z">
    <animateTransform attributeName="transform" attributeType="XML"
      type="scale" from="1" to="100"  begin="2s" dur="10s" fill="freeze"/>
    <animateColor to="red" attributeName="fill" begin="4s" dur="8s" fill="freeze"/>
    <animate attributeName="d" attributeType="XML" to="M 0,0 L 0,1 L 1,1 1,1 L 1,0 z"
      begin="5s" dur="9s" fill="freeze" />
  </path>
</svg>

Here, we are really pushing the power of animation to sort things out for us. The original colour is specified in a style attribute as a CSS property. But we can describe it as if it were an attribute value, and change it as with another animation. Again, the implementation does the work of calculating a smooth transition.

The attribute syntax that has been used for most CSS properties in the examples here is just a convenience. SVG uses (and extends) CSS for style, but it provides the attribute syntax for easy manipulation with XSLT and the like. We could just as well have described the presentation of the document using external style sheets, and still applied the same transformations.

Time to get practical

Let's begin a new example, something that moves and is useful. Example ex-c00.svg is a few lines that rotate around in a circle...

<?xml version="1.1"?>
<svg xmlns="http://www.w3.org/2000/svg"
     width="240px" height="240px" viewBox="0 0 240 240">
<g transform="translate(120,120) rotate(180)">
  <g>
    <line stroke-width="5" y2="80" stroke="black" opacity=".5" />
      <animateTransform attributeName="transform" type="rotate"
         repeatCount="indefinite" dur="12h" by="360" />
    <circle r="7" />
  </g>
  <g>
    <line stroke-width="4" y2="95" stroke="red" opacity=".9" />
      <animateTransform attributeName="transform" type="rotate"
         repeatCount="indefinite" dur="60min" by="360" />
    <circle r="6" fill="red"/>
  </g>
  <g>
    <line stroke-width="2" y2="100" stroke="blue" />
    <animateTransform attributeName="transform" type="rotate"
        repeatCount="indefinite" dur="60s" by="360" />
    <circle r="4" fill="blue"/>
  </g>
</g>
</svg>

One of them goes round in a minute, one in an hour, and one in twelve hours. This is just revision of things we have seen before, but in less than 25 short lines, we have a clock. Admittedly, it's not very exciting yet. It would be nice to have a proper clock. Let's start by adding a background. We have to put it before the clock hands, so that it gets painted underneath, and we get Example ex-c01.svg.

<?xml version="1.1"?>
<svg xmlns="http://www.w3.org/2000/svg"
     width="240px" height="240px" viewBox="0 0 240 240">
<g transform="translate(120,120)>

  <g>
    <circle r="108"  fill="red" stroke-width="4" stroke="#099" >
      <animateColor attributeName="fill" values="white;red;black;blue;white"
        dur="10s" repeatCount="infinite"/>
    </circle>
    <circle r="97" fill="none" stroke-width="9" stroke="white" 
       stroke-dasharray="4,46.789082" transform="rotate(-1.5)" />
    <circle r="100" fill="none" stroke-width="3" stroke="black"
       stroke-dasharray="2,8.471976" transform="rotate(-.873)" >
      <animateColor attributeName="fill" values="white;black;white"
        dur="10s" repeatCount="infinite"/>
    </circle>
  </g>

<!-- the actual clock hands go here, but they are unchanged, except the
     translate has been split off, so I left them out to keep this bit shorter.
     The example file has the full source, of course. -->

</g>
</svg>

This clock is now lovely. An exciting animated changing skin. It also shows a new animation feature - instead of simply providing a from and to value, we specify a list of values that the animation should step through. Of course, there is a very real risk that we will get sick of this animation. Let's see how we can have alternatives, for when we want to change it. This is not so difficult, and we get example c02.svg, which allows us to switch skins on our clock just by clicking on it.

<?xml version="1.1"?>
<svg xmlns="http://www.w3.org/2000/svg"
     width="240px" height="240px" viewBox="0 0 240 240">
<g transform="translate(120,120)">
  <g>
      <set attributeName="display" to="none" begin="b1.click" end="b2.click"/>
      <circle r="108" fill="#6f6" stroke-width="4" stroke="#090" id="b1" />
      <circle r="100" fill="none" stroke-width="3" stroke="black"
         stroke-dasharray="2,8.471976" transform="rotate(-.873)" />
      <circle r="97" fill="none" stroke-width="9" stroke="white" 
         stroke-dasharray="4,46.789082" transform="rotate(-1.5)" />
  </g>

  <g>
      <set attributeName="display" to="none" begin="b2.click" end="b1.click"/>
      <circle r="108"  fill="red" stroke-width="4" stroke="#099" id="b2" >
      <animateColor attributeName="fill" values="white;red;black;blue;white"
        dur="10s" repeatCount="infinite"/>
    </circle>
      <--  (I snipped the rest of the background, which is unchanged) -->
  </g>

<!-- the actual clock hands go here, but they are unchanged,
     so I left them out to keep this bit shorter.
     The example file has the full source, of course. -->

</g>
</svg>

Now we have two different backgrounds (the first one is not visible initially simply because it is covered by the second one). If you click on the background, it changes from one to the other. We have a simple swap, but we could equally have a chain of 3, or 27, backgrounds that we cycle through.

We are using another new element to perform our animation. set does as its name suggests - sets the (presentational) value of some attribute. Unlike the animations we have seen so far, this is not smooth but instantaneous. On the other hand it can be used in cases where there is no obvious way to make a smooth transition.

Yet another additional feature we are using is event-based animation, taking the start and end points from user interface events. In this case a click on the circle b1 or b2 (whichever is the visible background) activates one change and terminates the other. In order to do this, we have to give an id to the things we want to be triggers.

The transition in backgrounds here is basically instantaneous. But we could have used various other kinds of animation to provide an effect - scaling the background out over time, making one shrink towards the centre and then the other expand from the centre, as if flipping it over.

Pushing it...

The problem with our clock is that it always starts showing 12:00:00 - which is fine if you happen to turn it on at precisely that time, or you are around to pause it then. We could also tell it not to start moving until 12:00:00, using a wallclock value for begin. But this isn't how real clocks work. In the real world, if your clock is wrong, you can adjust it. Let's give our clock a winder that we can use to adjust it.

There are a few steps we want to take at once. We are going to use animations that explicitly name their targets, rather than simply acting on their parent. We are going to introduce yet another way to start and end an animation - this time, based on when another animation starts or ends.

The code snippets below show the changes that are necessary to give the final example, c03.svg. As always, you can look at the full source in the example (and this time it might be helpful).

<?xml version="1.1"?>
<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xl="http://www.w3.org/1999/xlink"
     width="240px" height="240px" viewBox="0 0 240 240">

<!-- We need to use the xlink namespace, for references around the document -->

<g transform="translate(120,120)">

<!-- Don't forget the background. It's included in the example code,
     but not here in the interests of space. -->

  <g transform="rotate(180)">
  <!-- We need to give the groups representing hands an id, to animate them from
     elsewhere. We also make them additive, so different rotations combine. -->
    <g id="h">
      <line stroke-width="5" y2="80" stroke="black" opacity=".5" />
        <animateTransform attributeName="transform" type="rotate"
           repeatCount="indefinite" dur="12h" by="360" additive="sum"/>
      <circle r="7" />
    </g>
      <-- likewise with id="m" for the minutes, s for seconds -->
  </g>

</g>

<!-- Now for the hard part. Starts off easily enough
     with the shapes we will use to make the winder.
     First the forward and backward buttons. -->
  <polygon id="up" points="0 10 0 2 2 0 8 0 10 2 10 10"
     transform="translate(230, 106)" />
  <polygon id="down" points="0 0 0 8 2 10 8 10 10 8 10 0"
     transform="translate(230, 124)" />
<!-- Now the reset button. -->
  <rect id="r" x="230" y="116" width="10" height="8" fill="red" /> 
<!-- Now, to move the time forward... -->
  <!-- We have a rotation, that applies to the thing with id="m" - the minute hand -->
  <!-- It's additive so it combines with other animations. It can repeat, but when it
       repeats it is the same as starting again, so it doesn't need to accumulate. -->
    <animateTransform attributeName="transform" type="rotate" xl:href="#m" id="mup"
         additive="sum" repeatCount="indefinite" dur="48s" by="4320" fill="freeze"
         begin="up.mousedown;r.click" end="up.mouseup;up.mouseout;r.click"/>
      <!-- It starts when the button is held down, and stops when let go, or moved off.
       But it also starts and immediately stops when the reset button is clicked. -->

  <!-- move the hour hand, by following the animation of the minute hand -->
     <animateTransform attributeName="transform" type="rotate" xl:href="#h"
         additive="sum" repeatCount="indefinite" dur="48s" by="360"  fill="freeze"
         begin="mup.begin" end="mup.end"/>
      
  <!-- Adjusting the time backward is so similar that I have left it out here -->

</svg>

Note how we used mup.begin and mup.end as triggers for our animations. This allows chaining animations together. We have also specified more than one trigger to start the animations. These are simple techniques that can give you a lot of power as you combine effects.

If you play with the winders, you'll notice something funnny happens. Specifically, you can wind forward once, and backward once, and it does what you expect. But if you wind forward several times, it resets itself each time. If you have wound forwards and backwards, and then try another one, it resets itself to a seemingly random point. Is this a bug? What's happening?

This is actually reasonable (and we are relying on it to produce the reset function). SMIL Animation 1.0, which is used in SVG, has no pause/resume markup. Instead, if you repeat an animation it restarts itself - i.e. removes its original effect and begins again. So if you have wound forward once, backward once, and wind forward again, the backward adjustment will still be in effect, but when you restart the forward winding, the first forward adjustment is reset so you start with the backward adjustment applied only - in other words, even further back.

Conclusion

We have seen how to use some basic animation in SVG to produce some interesting effects. We have seen some simple techniques for interactive or predetermined animations. We have also met some of the limits of animation. You should now know enough to use animation for a variety of tasks, so be able to produce a range of effects with simple SVG markup.

There is more to explore in animation. We have assumed that the implementation always controls the pace, making a smooth transition between values. But we could have used keyTimes and keySpline to vary the pace of the changes. We haven't looked at animateMotion which lets us move an object, or some text, along a path, around in a circle, and so on.

As always, you can look at the relevant part of the specification (in this case, the animation chapter of SVG 1.1) to see what is possible, and you should look at Opera's support documentation to check that what you are using has been implemented. But at least for now, you have what it takes to make some of your cool graphics even cooler...

Happy animating!

Implementation notes

Not all SVG players support animation. I have not included most of the animations in this document, but they are all linked. All of these work in Opera 9+, and are valid SVG so will work in any conformant SVG player. The entire declarative animation syntax for SVG is available in every SVG version, including 1.1 Tiny (the smallest of them all).

This article is licensed under a Creative Commons Attribution, Non Commercial - Share Alike 2.5 license.

Comments

The forum archive of this article is still available on My Opera.

  • photo

    Junhak Kim

    Friday, December 14, 2012

    great article!!!
No new comments accepted.