Animating Your SVG
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>
(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:
- Make the
rect
element into a pair of tags, so we can put something inside them - Put an
animate
element inside it, that says to change the attributewidth
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
andheight
x
,y
, (and yes,rx
andry
for rounded rectangle corners,cx
,cy
andr
for circles)opacity
, and by extensionstroke-opacity
andfill-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: (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
, orCSS
. The default value for this isauto
, 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
orskewY
, but here we want to make our little triangle bigger, so we change itsscale.
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.
Junhak Kim
Friday, December 14, 2012