Using SVG with CSS3 and HTML5 — Supplementary Material

Example code and online extras for the O'Reilly Media book by Amelia Bellamy-Royds, Kurt Cagle, and Dudley Storey.

Using SVG/SMIL Animation Elements

Without cross-browser support, the SVG/SMIL animation elements are of limited use on the web. But there are still numerous effects they can create that CSS animations and transitions can’t, so SVG/SMIL can be useful for quickly adding non-essential animations which don’t justify loading a JavaScript animation library.

To recap our introduction from the book: Most SVG/SMIL animations will use the <animate> element. Attributes on that element describe the changes you want to apply to an attribute or style property of another element in your SVG.

All the attributes for declaring animations#

There are a lot of different attributes for controlling SVG/SMIL animations. There is a more detailed reference, with all the syntax details, in “Animation Elements” section of our markup guide; this is a more conceptual overview.1 The attributes can be grouped into the following categories:

Setting the target element and attribute#

The target element is identified with an xlink:href cross-reference; it defaults to the animation element’s parent (which could be a shape, group, <use> element or pretty much any other element in SVG).

The target attribute or style property is set with attributeName. By default, the animation will affect the style property, if one exists by that name. You can use attributeType="XML" to force the browser to animate the XML attribute instead of a CSS property by the same name. This is important for animating width and height and so on.

Setting the new value(s)#

The changed value(s) for your attribute or style is set with one of the to, by, or values attributes, and optionally with from:

  • to is the final value of the property/attribute

  • by is an amount to increase or decrease the value

  • from is the initial value (which defaults to the base value on the element)

  • values is a sequence of keyframe values—including the “from” and “to” values—as a semicolon-separated list (if you use it, the other attributes are ignored)

The fill="freeze" attribute forces the ending value to be preserved indefinitely, unless you set a different animation on the same element or restart this animation.

Setting duration and repeats#

The dur attribute sets the duration time of a single cycle of the animation; without a fixed duration, the animation won’t progress past the from value. You can cause that cycle to repeat by setting repeatCount (a number of cycles) or repeatDur (a total duration time). By default (without either repeat* attribute), the animation only runs once.

Either repeatCount or repeatDur can be indefinite (not “infinite”) for unlimited repeats. If you set both repeat measurements, the first one to finish terminates the animation. You can also set a max (maximum) total duration time or a min (minimum) time; however, min can’t cause extra repeats, it only affects indefinite animations ended with end.

Setting beginning and ending times#

Animations have a begin time and an end time, which causes all repeats to end. begin is by default at 0s, relative to the document loading time; end is by default determined by the duration and repeat attributes. Both values can be a semicolon-separated list of multiple times.

In addition to beginning or ending at a specified time, you can begin or end an animation in response to an event. Events are specified using the DOM event name, like mouseover (for a mouseover event on the animation’s target element) or button1.click (for a click event on an element in the document with id of button1). Animation elements create begin and end events, plus numbered repeat events like repeat(1), so you can set one animation to begin or end in sync with another. You can also set time offsets from an event, like begin="otherAnimation.end - 1s" to start this animation one second before the other animation is due to end.

Multiple begin times or event triggers can cause an animation to interrupt itself; the restart attribute can limit restarts, to only whenNotActive (i.e., not currently running) or never at all.

Setting pacing between keyframes#

By default, multiple keyframe values are evenly distributed in the duration of an animation cycle, and the actual values are interpolated in a linear fashion between each keyframe. You can change the keyframe positions with keyTimes, which take a semicolon-separated list of numbers between 0 and 1 (like the percentages in CSS keyframes, but converted to decimals).

You can switch interpolation methods with the calcMode attribute, to discrete, paced, or spline (the default is linear). “Spline” interpolation is cubic Bézier easing, and only works if you also set keyTimes and keySplines. The values in keySplines are cubic Bézier control points (four numbers, like the ones in the CSS cubic-bezier() function), with multiple sets of four numbers (one for every pair of consecutive values) in a semicolon-separated list.

Setting additive effects#

The additive and accumulate attributes allow animations or repeats to sum together; we’ll discuss them in “Additive and accumulative animations”.

Are you confused yet?

Learning SVG/SMIL takes a lot of practice and a lot of double-checking the specs. There are so many possible options, but there are also lots of annoying cross-dependencies, like not being able to use from without either to or by, and not being able to use keySplines without keyTimes, even when you aren’t changing the keyTimes default!

So why bother?

Because there are some things that SVG/SMIL can do really easily that are currently difficult or impossible with CSS animations. The next couple sections look at some of those cases.

Synchronized animation timelines#

One of the biggest benefits of SVG/SMIL relative to CSS animations are the synchronized timing options. These allow you to create an entire script of different short animations.

To demonstrate, we’ll create an animated logo, similar to the “stingers” that movie and TV production studios use at the beginning or ends of films. The logo is about as basic as a logo can get, as shown in Figure 19-X1: a circle, with the words “Circle Games” arranged in a circular <textPath>.

The circle is purple with a lighter purple stroke.  The 'Circle Games' text is in a sans-serif font, inset from the edges of the circle and centered at the top, wrapping down on either side in a semi-circle shape.
Figure 19-X1. A simple logo, ready for animation

To give this simple logo a little pizzazz, we’re going to:

Figure 19-X2 shows the animation sequences as a series of stills, taken at one-second intervals for twenty seconds. For something a little more exciting, view the final live animation.

Twenty squares arranged in rows of five.  Frame 1 is an empty white screens.  Frames 2 through 5 are purple circles alternating larger and smaller.  Frames 10 through 14 all have the same-sized circle, instead text is change.  Letters appear upside-down and curves 'ames' then 'cle Games' and finally 'Circle Games' as they wrap around the circle, eventually being positioned in a semi-circle at the top of the logo, where it stays for three frames before fading away.  After that, in Frames 15 to 18, the circle shrinks to a dot in the middle of the frame.  In frame 19, the dot is off to one side. In frame 20, it is gone, leaving behind the blank white screen.
Figure 19-X2. Frames of a logo animation, at one-second intervals
Tip

The SVG DOM has methods for SVG/SMIL animations that can help with taking still screenshots—among other uses.

Every <svg> element has pauseAnimations() and unPauseAnimations() methods, plus a setCurrentTime() method that accepts a time in seconds. These all affect all animations in the document.

Each animation element also has beginElement() and endElement() methods.

Before we get into the code, we’ll introduce one more animation element.

Although the <animate> element is the general workhorse for most attribute animations, for simple discrete changes it is usually easier to use <set>.

A <set> element applies the to value to the target attribute or property for as long as the animation is active. In other words, it sets the attribute to the new value for a specified amount of time (dur) or until the end trigger. There is no transition time—the change is always discrete—and there is no way to apply a sequence of values or to change the value by a relative amount.

In other words, the main benefit of <set> is that it lets you avoid writing calcMode="discrete" over and over again. Also, it makes your code a little more clear.

So the following <set> element would ensure that its parent element disappears when the element with id="button1" is clicked, and isn’t displayed again until the element with id="button2" is clicked:

<set attributeName="display" to="none"
     begin="button1.click"
     end="button2.click" />

(Of course, for accessibility purposes you’d want make sure that button1 and button2 are actually <a> links that can be “clicked” with the keyboard, too.)

Warning

Theoretically, the event trigger elements could be other (non-SVG) elements in the same document. In other words, they could be HTML <button> elements in a document with inline SVG. But that doesn’t work in Chrome, which only listens for these events on SVG elements.

In Example 19-X1, we use a <set> to hide the logo text until it is ready to be animated in. The remaining changes are all defined with <animate> elements, that modify the circle’s radius (r), or its horizontal position (cx), the text’s opacity, and the <textPath>’s startOffset position.

Example 19-X1. Creating a complex animation sequence from synchronized animation elements
<svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     width="100%" height="100%" viewBox="-220 -220 440 440">
    <title>Reveal and Disappear Animated Logo</title>
    <circle id="c" r="190"
            fill="darkOrchid" stroke="plum" stroke-width="16" /> 1
    <defs>
        <path id="p" d="M0,125 A125,125 0 0,1 0,-125
                               A125,125 0 0,1 0, 125Z" />
    </defs>                                                      2
    <text id="t" dy="0.7ex" text-anchor="middle"
          font-size="48pt" font-family="Verdana"
          fill="white">
        <textPath id="tp" xlink:href="#p"
                  startOffset="50%">Circle Games</textPath></text>

    <set     id="textHide" xlink:href="#t"
             attributeName="visibility" to="hidden"
             end="textFadeIn.begin" />                   3
    <animate id="circleGrow" xlink:href="#c"
             attributeName="r"
             values="0;215;100;205;140;195;170;190"
             dur="5s"/>                                  4
    <animate id="textPathSlide" xlink:href="#tp"
             attributeName="startOffset"
             from="-20%" to="50%"
             dur="4s" begin="circleGrow.end - 1s" />     5
    <animate id="textFadeIn" xlink:href="#t"
             attributeName="opacity" from="0" to="1"
             dur="3s" begin="textPathSlide.begin" />     6
    <animate id="textFadeOut" xlink:href="#t"
             attributeName="opacity" to="0" fill="freeze"
             dur="2s" begin="textPathSlide.end + 3s" />  7
    <animate id="circleShrink" xlink:href="#c"
             attributeName="r" to="8" fill="freeze"
             dur="3s" begin="textFadeOut.end + 0.1s"
             calcMode="spline" keyTimes="0;1"
             keySplines="0.2,0.9,0.9,0.5" />             8
    <animate id="circleMove" xlink:href="#c"
             attributeName="cx" by="-500" fill="freeze"
             dur="3s" begin="circleShrink.end"
             calcMode="spline" keyTimes="0;1"
             keySplines="0.5,0,0.8,0" />                 9
</svg>
1

To keep the file organized, all the code for drawing the shapes and text is given first, then all the animation elements are listed together, ordered by their start time. We’ve also given all the animation elements id values for consistency (and to document the code), although only some of them are used to synchronize begin/end times.

2

The graphic is a stroked circle and then a <textPath>. The <path> for the <textPath> isn’t drawn itself; it describes a 125px-radius circle that starts and ends at the bottom.

3

The first animation is the <set> that hides the text until it’s needed. It starts at 0s (by default) and is ended by the beginning of the textFadeIn animation.

4

The circleGrow animation also begins by default as soon as the document loads. It modifies the circle’s r attribute, in an alternating series of values spread out over 5 seconds.

5

The motion of the text on the path (textPathSlide), created by animating the startOffset attribute, starts one second before the circle animation has completely settled.

6

The textFadeIn animation (changing opacity) is synchronized to start with textPathSlide, and it in turn triggers the end of the textHide <set> element. All of these timing connections are based on the logical dependencies of the content, so that if the timing of one step is tweaked later, other animations will adjust to match.

7

The fade-out starts three seconds after the end of the text motion, and freezes the text as transparent.

8

A mere tenth of a second after the text disappears, the circle starts shrinking down to the 8px radius that will mean that only the stroke is visible. The motion uses a cubic Bézier spline that starts relatively fast, slows down, then speeds up again. You can calculate these spline values using the same tools as for CSS cubic-bezier() timing functions.

9

The final animation, that moves the circle left by 500px, also uses spline easing, this time to create an accelerating effect. The final position is frozen, well off screen (even if the viewBox doesn’t take up the full <svg> dimensions).

The base values for all those attributes are defined such that, without SVG/SMIL support, the static logo is shown, with the circle full size and the text neatly centered.

Animating complex motion#

In addition to <animate> and <set>, there are two specialized SVG animation elements for creating animated transformations:

<animateTransform>

Applies one particular transformation function to the target element. The type attribute specifies which type of transformation (translate, scale, rotate, etc.), while the to, from, by, or values attributes specify the parameters to the transformation.

<animateMotion>

Moves the target element, similar to translational (and rotational) transformations. However, instead of defining the transformation using the normal syntax, you describe where you want the element to go. The path of the motion can be defined as a series of translations, or as an SVG path through the coordinate system.

You can optionally make the shape auto-rotate to match the angle of the path (similar to how the letters are rotated in a <textPath> layout), with rotate="auto".

Warning

The SVG 1 spec also included the SMIL <animateColor> element, but it didn’t add any function, because <animate> could animate colors in SVG. The element was deprecated in SVG 1.1 and is no longer supported in Chrome. Use <animate> instead.

In SVG 1.1, <animateTransform> and <animateMotion> are the only way to get smooth transitions of tranformations: the spec did not define rules for animating the full transformation lists from one value to another. Those rules are defined in the CSS Transforms Module, so you could theoretically animate transforms with <animate>, but only if any of the browsers had updated their implementations.

We’ll show an example of <animateTransform> in the next section, when we discuss additive animations. For this section, we’re focusing on <animateMotion>.

In the original SMIL version of <animateMotion>, you specified x and y translation distances in the to, from, by, or values attributes.

You can do that with SVG/SMIL, but it is mostly equivalent to an <animateTransform> with type="translate". The differences are:

A paced animation means that the speed stays steady throughout the entire animation. In contrast, with linear and spline modes, the total duration is divided up into separately-timed transitions for each value in the list. Consider the following animation:

<animateMotion values="0,30; 40,0; 80,30; 0,30"
               rotate="auto"
               dur="9s" repeatCount="indefinite" />

Those four (x,y) pairs in values, with the last pair repeating the first, create an isoceles triangle, as shown in Figure 19-X3. The sides of the triangle have lengths of 50, 50, and 80 units; the total path length is 180 units.

Two copies of a figure featuring a wide isoceles triangle drawn with a dashed line.  In the top version, labelled linear, each side is labelled 3s.  Bottom is labelled paced, and the sides are labelled 2.5s, 2.5s, and 4s.  A red arrow is positioned on each triangle.  In the top, it is approaching the corner from the second short side to the long base.  In the bottom, it is already partway along the base.
Figure 19-X3. Timing of triangular motion with linear versus paced calcMode

A linear mode would divide the total animation into 3 equal steps (unless you set keyTimes). For the 9s animation, that would mean 3 seconds for each side of the triangle. The shape would move more slowly on the shorter sides and faster on the long side.

For the paced animation, the total 180-unit distance would be divided by the 9s total duration; any keyTimes attribute would be ignored. The shape moves 20 units per second along the entire triangle.

Tip

You can also set calcMode="paced" for any animation where the values are numbers or colors. (The same types of properties that work with additive animations.) The “distance” for colors is the distance across the RGB cube, as if each color channel was a different direction in 3D space.

The animation will pass through all the values you specify, in the order you give them, but the overall rate of change will stay constant.

The most interesting use of <animateMotion> is to apply motion along an SVG path. This allows you to define motion along curves which can’t be broken down into a series of linear translations. It also means that Chrome will support auto-rotation.

The motion path you use can be set two ways:

The <mpath> element has no other attributes; it only exists because the main animation elements already had an xlink:href attribute for identifying the target element—and because SVG 1 insisted on using xlink:href for all cross references other than style properties.

Example 19-3, in the book, used <animateMotion> and <mpath> to create an infinitely spinning loader, of multiple beads moving along the same path.

Additive and accumulative animations#

Compared to the complex patterns you can create with <animateMotion>, a single <animateTransform> element isn’t particularly interesting. It can only apply a single transformation function: either a rotation or a translation or a scale or a skew.

You could create complex motion with <animateTransform> by nesting elements with different transformations: an element with a translation inside a group with a rotation, for example. Nesting elements like this is currently the recommended approach for animating complex transformations with CSS.

But sometimes, you don’t want to add all those nested <g> elements. And sometimes, it’s not even an option—for example, inside a <clipPath> where groups are still forbidden.

SVG/SMIL has a solution: additive animations. Multiple independent animation elements, animating the same property on the same element, that can add together in their effect.

Tip

Additive animations are particularly useful for <animateTransform>, but can also be used for most numerical or length attributes, or for colors.

You define an additive animation by setting the additive attribute to sum.

The default value for additive is replace: animations that start later replace earlier animations of the same property on the same element. For multiple animations that start at the same time, the last element in the document wins (similar to CSS rules).

Example 19-X2 uses additive <animateTransform> animations to create a complex motion pattern. The example creates the appearance of X-Ray glasses or goggles, seeing through a photograph (of one of the authors, Kurt). Figure 19-X4 shows sample screenshots.

Two copies of a photograph of Kurt in a top hat.  At different positions on each is a green outline of two side-by-side circles, like binoculars.  Through the binocular outline, the photograph is somewhat different: still a man in glasses and a top hat with a bushy white beard, but instead of rosy cheeks and crinkled eyes there is an empty skull with grinning teeth.
Figure 19-X4. Two X-ray views into Kurt’s skull (oh my!)
Example 19-X2. Creating complex transformation patterns with additive animations
<svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     viewBox="0 0 384 643" width="384px" height="643px">
    <title>X-Ray Scanner</title>
    <image xlink:href="TopHatKurt.jpg" width="100%" height="100%"/>
    <image xlink:href="TopHatSkull.jpg" width="100%" height="100%"
           clip-path="url(#cp)" />                         1
    <path id="goggles"
          d="M0-30 A75,75 0 1,1 0,30 A75,75 0 1,1 0,-30Z"
          fill="green" fill-opacity="0.4"
          stroke="green" stroke-width="4">                 2
        <animateTransform attributeName="transform"
            type="translate" values="200,40;200,600;200,40"
            dur="10s" repeatCount="indefinite" />          3
        <animateTransform attributeName="transform"
            type="translate" values="-50,0;50,0;-50,0"
            dur="3s" repeatCount="indefinite"
            additive="sum"/>                               4
        <animateTransform attributeName="transform"
            type="rotate" values="-10;10;-10"
            dur="7s" repeatCount="indefinite"
            additive="sum"/>                               5
    </path>
    <clipPath id="cp">
        <use xlink:href="#goggles" />                      6
    </clipPath>
</svg>
1

Most of the magic of this example comes from the two perfectly matched JPEG photographs, one of which has been edited to create a black-and-white “X-ray” view. The X-ray photo is drawn on top, but is clipped to only be visible through the “goggles”.

2

The X-ray goggles are drawn with a path containing two large circular arcs, centered on the origin. The path has a semi-transparent green fill—through which we’ll see the image below—and a solid green outline.

3

The first <animateTransform> element positions the goggles in the horizontal center of the SVG, and then moves them down and back up over 10 seconds, from y=40 to y=600, keeping the x-position constant at 200. It (and the other animations) repeats indefinitely.

4

The second <animateTransform> translates the x values back and forth while keeping y steady. The additive="sum" attribute is essential, so this translation is added to the previous one, instead of replacing it.

5

The third <animateTransform> is of type rotate, rocking the goggles 10° in either direction; again, this transformation adds on to the previous ones.

6

The final, animated <path> is cloned into the <clipPath> which is used for the X-Ray photograph. The clipping of the photograph therefore stays in sync with the visible goggle outline.

Example 19-X2 demonstrates another limitation of the SMIL syntax: in order to create an alternating (back-and-forth) animation, you need to repeat values in an a;b;a sequence in the values attribute.

Aslo note that the attributeName is required, even for <animateTransform>: the <animateTransform> element can also animate gradientTransform and patternTransform attributes.

Warning

The original version of Example 19-X2 animated a shape inside <defs>, and then drew the green outline with a <use>. However, Firefox (since version 31) has a bug where the <use> doesn’t get updated with the animations—except for when the <use> is inside the <clipPath>, when it is strangely unaffected by the bug.

Since the <clipPath> effect ignores strokes and fill, we were able to work around the bug by drawing the visible element directly and then re-<use>-ing it in the clipping path.

The motion of the X-ray goggles, as they scan the image, consists of three components:

None of these animations would be particularly interesting on their own; it is the additive effect that creates the complex motion.

Because none of the durations of the animations are even multiples of the other, the cumulative pattern appears imprecise and more natural. It does not repeat exactly until three and a half minutes have passed (210s, the least common multiple of 10, 3, and 7).

Note

Creating pseudo-random patterns by layering together multiple repeating elements that don’t match up precisely is known as the cicada principle. Cicadas are insects with multi-year lifespans, most of which is spent underground. Some species have very predictable lifespans, but because the cycles don’t line up from one species to another, you can get uneven bursts in the adult population numbers.

Or something like that. It doesn’t really have anything to do with the insects, it’s just a name for the design technique.

In addition to its use in animations, the cicada principle can be used in SVG to create complex visual patterns by layering together semi-transparent repeating patterns or gradients. The sizes of the repeats—whether in visual space or in time—should not divide neatly one into the other.

In addition to additive animations, which is one animation element adding on to another, SVG/SMIL defines accumulative animations, where each subsequent repeat of an animation adds on to previous repeats.

The relevant attribute is accumulate and the non-default value is once again sum. (The default value is none.)

This is most useful if you want to create an animation that proceeds in steps, instead of in a single smooth transition. Without accumulation, this would require setting a large list of intermediary values, so you could set discrete transitions or individual easing curves for each setp.

With accumulation, the animation describes the relative change for each step and the number of repeats required to reach the final sum. The calcMode and related attributes describe how each step should progress.

Example 19-X3 gives the code for an accumulative animation increasing the radius of a circle. Each cycle uses keySplines easing to add a pauses, puffing out quickly and then slowing at the end of the repeat. It’s not much to look at in a static screenshot, but you can view the live SVG in a browser with SVG/SMIL support. The appearance is somewhat like a balloon being inflated by a series of big breaths.

Example 19-X3. Using accumulative repeats and spline easing to create a pulsed animation
<svg xmlns="http://www.w3.org/2000/svg" xml:lang="en"
     viewBox="-80 -80 160 160" width="4in">
    <title>Accumulative Animations</title>
    <circle r="75" fill="crimson">
        <animate attributeName="r"
                 from="0"
                 by="10"
                 calcMode="spline"
                 keyTimes="0;1"
                 keySplines="0.1,1 0.9,0"
                 dur="1s"
                 repeatCount="7.5"
                 accumulate="sum" />
    </circle>
</svg>
Warning

The original version of this demo used a keySplines value of 0.3,2,0.7,-1 to create a “bounce” effect on each repeat. It worked in Chrome—and in CSS timing functions—but is contrary to the SMIL specs and not supported in Firefox. The keySplines values must all be between 0 and 1.

Example 19-X3 also demonstrates that repeatCount does not have to be an integer; here, it takes 7.5 repeats to sum up to the desired radius.


1 SVG Essentials, Second Edition, by J. David Eisenberg and Amelia Bellamy-Royds, has a slightly more comprehensive discussion.