A Guide to SVG Animations (SMIL)
The following is a guest post by Sara Soueidan. Sara has a knack for digging deep into web features and explaining the heck out of them for the rest of us. Here she digs into SMIL (and friends), and animation syntax built right into SVG, and gives us this epic guide.
Overview
SVG graphics can be animated using animation elements. The animation elements were initially defined in the SMIL Animation specification; these elements include:
<animate>
– which allows you to animate scalar attributes and properties over a period of time.<set>
– which is a convenient shorthand for animate, which is useful for assigning animation values to non-numeric attributes and properties, such as the visibility property.<animateMotion>
– which moves an element along a motion path.<animateColor>
– which modifies the color value of particular attributes or properties over time. Note that the<animateColor>
element has been deprecated in favor of simply using the animate element to target properties that can take color values. Even though it’s still present in the SVG 1.1 specification, it is clearly noted that it has been deprecated; and it has been completely removed from the SVG 2 specification.
In addition to the animation elements defined in the SMIL spec, SVG includes extensions compatible with the SMIL animations spec; these extensions include attributes that extend the functionality of the <animateMotion>
element and additional animation elements. The SVG extensions include:
<animateTransform>
– allows you to animate one of SVG’s transformation attributes over time, such as thetransform
attribute.path
(attribute) – allows any feature from SVG’s path data syntax to be specified in a path attribute to theanimateMotion
element (SMIL Animation only allows a subset of SVG’s path data syntax within a path attribute). We’ll talk more aboutanimateMotion
in an upcoming section.<mpath>
– used in conjunction with theanimateMotion
element to reference a motion path that is to be used as—well—a path for the motion. The mpath element is included inside theanimateMotion
element, before the closing tag.keypoints
(attribute) – used as an attribute foranimateMotion
to provide precise control of the velocity of motion path animations.rotate
(attribute) – used as an attribute foranimateMotion
to control whether an object is automatically rotated so that its x-axis points in the same direction (or opposite direction) as the directional tangent vector of the motion path. This attribute is the key to making motion along a path work as you’d expect. More about this in theanimateMotion
section.
SVG animations can be similar to CSS animations and transitions via by their nature. Keyframes are created, things move, colors change, etc. However, they can do somethings that CSS animations can’t do, which we’ll cover.
Why Use SVG Animations?
SVGs can be styled and animated with CSS (slides). Basically, any transformation or transition animation that can be applied to an HTML element can also be applied to an SVG element. But there are some SVG properties that cannot be animated through CSS that can through SVG. An SVG path, for example, comes with a set of data (a d=""
attribute) that defines that path’s shape. This data can be modified and animated through SMIL, but not CSS. This is because SVG elements are described by a set of attributes known as SVG presentation attributes. Some of these attributes can be set, modified, and animated using CSS, and others can’t.
So, many animations and effects can simply not be achieved using CSS at this time. The CSS SVG animation gaps can be filled by using either JavaScript or the declarative SVG animations derived from SMIL.
If you prefer using JavaScript, I recommend using snap.svg by Dmitry Baranovsky, which is described as being “the jQuery of SVG”. Here’s a collection of examples of that.
Or if you prefer a more declarative animation approach, you can use the SVG animation elements as we’ll cover in this guide!
Another advantage to SMIL over JS animations is that the latter two don’t work when the SVG is embedded as an img
or used as a background-image
in CSS. SMIL animations do work in both cases (or should, browser support pending). That’s a big advantage, in my opinion. You may find yourself choosing SMIL over other options because of that. This article is a guide to help you get started using SMIL today.
Browser Support and Fallbacks
Browser support for SMIL animations is pretty decent. They work in all browsers except in Internet Explorer and Opera Mini. For a thorough overview of browser support, you can refer to the compatibility table on Can I Use.
If you need to provide a fallback for SMIL animations, you can test for browser support on-the-fly using Modernizr. If SMIL is not supported, you can then provide some kind of fallback (JavaScript animations, an alternate experience, etc).
Specifying the target of the animation with xlink:href
No matter which of the four animation elements you choose, you need to specify the target of the animation defined by that element.
In order to specify a target, you can use the xlink:href
attribute. The attribute takes a URI reference to the element which is the target of this animation and which therefore will be modified over time. The target element must be part of the current SVG document fragment.
<rect id="cool_shape" ... />
<animation xlink:href="http://css-tricks.com/#cool_shape" ... />
If you’ve come across SVG animation elements before, you’ve probably seen them nested inside the element that they’re supposed to animate. This is possible as well as per the spec:
If the
xlink:href
attribute is not provided, then the target element will be the immediate parent element of the current animation element.
<rect id="cool_shape" ... >
<animation ... />
</rect>
So if you want to “encapsulate” the animation into the element it applies to, you can do that. And if you want to keep the animations separate somewhere else in the document, you can do that too, and specify the target of each animation using xlink:href
— both ways work just fine.
Specifying the target property of the animation with attributeName
and attributeType
All animation elements also share another attribute: attributeName
. The attributeName
attribute is used to specify the name of the attribute that you’re animating.
For example, if you want to animate the position of the center of a <cirlce>
on the x-axis, you do that by specifying cx
as the value for the attributeName
attribute.
attributeName
takes only one value, not a list of values, so, you can only animate one attribute at a time. If you want to animate more than one attribute, you need to define more than one animation for the element. This is something that I wish were different, and that I think CSS has an advantage over SMIL for. But then again, because of the values possible for other animation attributes (which we’ll cover next), it only makes sense to define only one attribute name at a time, otherwise the other attribute values can become too complex to work with.
When you specify the attribute name, you can add an XMLNS (short for XML namespace) prefix to indicate the namespace of the attribute. The namespace can also be specified using the attributeType
attribute. For example, some attributes are part of the CSS namespace (which means that the attribute can be found as a CSS property as well) and others are XML-only. A table showing these attributes can be found here. The attributes in the table are not all of the SVG attributes. They are only the ones that can be set using CSS. Some of them are already available as CSS properties.
If the value for attributeType
is not explicitly set or is set to auto
, the browser must first search through the list of CSS properties for a matching property name, and if none is found, search the default XML namespace for the element.
For example, the following snippet animates the opacity
of an SVG rectangle. Since the opacity
attribute is also available as a CSS property, the attributeType
is set to the CSS namespace:
<rect>
<animate attributeType="CSS" attributeName="opacity"
from="1" to="0" dur="5s" repeatCount="indefinite" />
</rect>
We’ll go over the other animation attributes in the upcoming examples below. Except where otherwise noted, all of the animation attributes are common to all of the animation elements.
Animating an element’s attribute from one value to another over a duration of time, and specifying the end state: from
, by
, to
, dur
and fill
Let’s start by moving a circle from one position to another. We’re going to do that by changing the value of its cx
attribute (which specifies the x-position of its center).
We’re going to use the <animate>
element to do that. This element is used to animate one attribute at a time. Attributes that take numerical values and colors are usually animated with <animate>
. For a list of attributes that can be animated, refer to this table.
In order to change a value to another over a period of time, the from
, to
, and dur
attributes are used. In addition to these, you will also want to specify when the animation should start—for this you use the begin
attribute.
<circle id="my-circle" r="30" cx="50" cy="50" fill="orange" />
<animate
xlink:href="http://css-tricks.com/#my-circle"
attributeName="cx"
from="50"
to="450"
dur="1s"
begin="click"
fill="freeze" />
In the above example, we’ve defined a circle, and then called an animation on that circle. The center of the circle moves from the initial position at 50 units, to 450 units along the x-axis.
The begin
value is set to click
. This means that the circle will move when it is clicked. You can set this value to a time value as well. For example, begin="0s"
will start the animation as soon as the page is loaded. You can delay an animation by setting a positive time value—for example, begin="2s"
which starts the animation two seconds after load.
What’s even more interesting about begin
is that you can define values like click + 1s
to start an animation one second after the element is clicked! What’s more, you can use other values that allow you to sync animations without having calculate the duration and delays of other animations. More about this later.
The dur
attribute is similar to the animation-duration
equivalent in CSS.
The from
and to
attributes are similar to the from
and to
keyframes in an animation’s @keyframe
block in CSS:
@keyframes moveCircle {
from { /* start value */ }
to { /* end value */ }
}
The fill
attribute (which is rather unfortunately named the same as the fill
attribute which defines the fill color of an element) is similar to the animation-fill-mode
property, which specifies whether or not the element should return to its initial state after the animation is over. The values in SVG are similar to those in CSS, except use different names:
freeze
: The animation effect is defined to freeze the effect value at the last value of the active duration. The animation effect is “frozen” for the remainder of the document duration (or until the animation is restarted).remove
: The animation effect is removed (no longer applied) when the active duration of the animation is over. After the active end of the animation, the animation no longer affects the target (unless the animation is restarted).
Try changing the values in the live demo to see how the animation is affected:
Open this live demo on CodePen.
The by
attribute is used to specify a relative offset for the animation. As the name suggests, you can use it to specify the amount by which you want the animation to progress. The effect of by
is almost only visible when you’re progressing over the animation duration in discrete steps, similar to the way it works with the CSS steps()
function. The SVG equivalent to the CSS steps()
function is calcMode="discrete"
. We’ll get to the calcMode
attribute later in the article.
Restarting Animations with restart
It may be useful to prevent an animation from being restarted while it is active. To do that, SVG offers the restart
attribute. You can set this attribute to one of three possible values:
always
: The animation can be restarted any time. This is the default value.whenNotActive
: The animation can only be restarted when it is not active (i.e. after the active end). Attempts to restart the animation during its active duration are ignored.never
: The element cannot be restarted for the remainder of the current simple duration of the parent time container. (In the case of SVG, since the parent time container is the SVG document fragment, then the animation cannot be restarted for the remainder of the document duration.)
Naming animations and synchronizing them
Suppose we want to animate the position and the color of the circle, such that the change in color happens at the end of the moving animation. We can do that by setting the begin
value of the color-changing animation to be equal to the dur
ation of the moving animation; this is how we would normally do it in CSS.
SMIL, however, has a nice event-handling feature. We mentioned before that the begin
attribute accepts values like click + 5s
. This value is called an “event value”, and is in this case made up of an event reference followed by a “clock value”. The interesting part here is the naming of the second part: the “clock value”. Why is it not simply a “time value”? Well the answer is that you can literally use a clock value like “10min” or “01:33” which is equivalent to “1 minute and 33 seconds”, or even “02:30:03” (two hours, 30 minutes, and 3 seconds). Pretty interesting – but unfortunately they don’t work in any browser.
So, if we were to go back to the previous demo and use click + 01:30
, if a browser started supporting it, the animation would fire 1 minute and 30 seconds after the circle is clicked.
Another kind of value it can accept is the ID of another animation followed by an event reference. If you have two (or more) animations (whether they are applied to the same element or not!) and you want to synchronize them so that one of them starts relative to the other, you can do that without having to know the duration of the other animation.
For example, in the next demo, the blue rectangle starts moving 1 second after the circle animation starts. This is done by giving each animation an ID
, and then using that ID with the begin
event as shown in the following code:
<circle id="orange-circle" r="30" cx="50" cy="50" fill="orange" />
<rect id="blue-rectangle" width="50" height="50" x="25" y="200" fill="#0099cc"></rect>
<animate
xlink:href="http://css-tricks.com/#orange-circle"
attributeName="cx"
from="50"
to="450"
dur="5s"
begin="click"
fill="freeze"
d="circ-anim" />
<animate
xlink:href="http://css-tricks.com/#blue-rectangle"
attributeName="x"
from="50"
to="425"
dur="5s"
begin="circ-anim.begin + 1s"
fill="freeze"
id="rect-anim" />
The begin="circ-anim.begin + 1s"
is the part that tells the browser to start the rectangle’s animation 1 second after the beginning of the circle’s. You can check the live demo out:
Open this live demo on CodePen.
You can also start the rectangle animation after the circle animation ends using the end
event:
<animate
xlink:href="http://css-tricks.com/#blue-rectangle"
attributeName="x"
from="50"
to="425"
dur="5s"
begin="circ-anim.end"
fill="freeze"
id="rect-anim"/>
You could even start it before the end of the circle’s animation:
<animate
xlink:href="http://css-tricks.com/#blue-rectangle"
attributeName="x"
from="50"
to="425"
dur="5s"
begin="circ-anim.end - 3s"
fill="freeze"
id="rect-anim"/>
Repeating Animations with repeatCount
If you want to run an animation more than once, you can do that using the repeatCount
attribute. You can specify the number of times you want it to repeat, or use the indefinite
keyword to have it repeat endlessly. So, if we were to repeat the circle’s animation for two times, the code would look like so:
<animate
xlink:href="http://css-tricks.com/#orange-circle"
attributeName="cx"
from="50"
to="450"
dur="5s"
begin="click"
repeatCount="2"
fill="freeze"
id="circ-anim" />
You can check the live demo out here. In the demo, I’ve set the repeat count to be 2 on the circle, and indefinite
on the square.
Open this live demo on CodePen.
Notice how the animation restarts from the initial from
value instead of the value it reached at the end of the animation. Unfortunately, SMIL does not include a way to go back and forth between the start and end values like CSS animations allow us to do. In CSS, the animation-direction
property specifies whether or not an animation should play in reverse on some or all cycles or iterations. animation-direction: alternate
value means that the animation cycle iterations that are odd counts are played in the normal direction, and the animation cycle iterations that are even counts are played in a reverse direction. This means that the first cycle will play from beginning to end, then the second cycle will play from the end back to the beginning, then the third cycle will play from the beginning to the end, and so on.
In SMIL to do that you would have to use JavaScript to explicitly change the values of the from
and to
attributes. Jon McPartland of the Big Bite Creative wrote a post a while back explaining how he did this for a menu icon animation that he worked on.
Here’s a nice, simple infinite animation using some delayed begin times by Miles Elam:
See the Pen Hexagon Ripple by Miles Elam (@mileselam) on CodePen.
Restricting repetition time with repeatDur
Setting an element to repeat indefinitely may get annoying or not user-friendly if the animation resumes for a long time. So, it may be a good idea to restrict the repetition time to a certain period of time, and stop the repetition after some time relative to the beginning of the document—this is known as the presentation time.
The presentation time indicates the position in the timeline relative to the document begin of a given document fragment. It is specified using the repeatDur
attribute. Its syntax is similar to that of a clock value, but instead of being relative to another animation event or an interaction event, it’s relative to the beginning of the document.
For example, the following snippet will stop the repetition of the animation 1 minute and 30 seconds after the document begin:
<animate
xlink:href="http://css-tricks.com/#orange-circle"
attributeName="cx"
from="50"
to="450"
dur="2s"
begin="0s"
repeatCount="indefinite"
repeatDur="01:30"
fill="freeze"
id="circ-anim" />
And here is the live demo:
Synchronizing animations based on number of repetitions
Now let’s go back a step to the synchronizing between two animations topic. Indeed, in SMIL, you can synchronize animations so that one animation starts based on the number of repetitions of another. For example, you can start an animation after the nth-repetition of another, plus or minus an amount of time you may want to add.
The following example starts the rectangle’s animation at the second repetition of the circle’s animation:
<animate
xlink:href="http://css-tricks.com/#blue-rectangle"
attributeName="x"
from="50"
to="425"
dur="5s"
begin="circ-anim.repeat(2)"
fill="freeze"
id="rect-anim" />
The sync based on repetitions is currently not implemented in any browser, so unfortunately there is no live demo for this section.
Controlling animation keyframe values: keyTimes
and values
In CSS, we can specify the values that we want our animated property to take in a certain frame during the animation. For example, if you’re animating the left offset of an element, instead of animating it from, say, 0 to 300 directly, you can animate it so that it takes certain values during certain frames like this:
@keyframes example {
0% {
left: 0;
}
50% {
left: 320px;
}
80% {
left: 270px;
}
100% {
left: 300px;
}
}
The 0%, 20%, 80%, and 100% are the frames of the animation, and the values in each frame’s block are the values for each frame. The effect described above is one of an element bouncing off a wall, then back to the final position.
In SMIL, you can control the values per frame in a similar way, but the syntax is quite different.
To specify the keyframes, you use the keyTimes
attribute. And then to specify the value of the animated property for each frame, you use the values
attributes. The naming conventions in SMIL are quite convenient.
If we were to go back to our moving circle, and use values similar to the ones in the CSS keyframes above, the code will look like the following:
<animate
xlink:href="http://css-tricks.com/#orange-circle"
attributeName="cx"
from="50"
to="450"
dur="2s"
begin="click"
values="50; 490; 350; 450"
keyTimes="0; 0.5; 0.8; 1"
fill="freeze"
id="circ-anim" />
So what did we do there?
The first thing to notice here is that the keyframe times and intermediate values are specified as lists. The keyTimes
attribute is a semicolon-separated list of time values used to control the pacing of the animation. Each time in the list corresponds to a value in the values
attribute list, and defines when the value is used in the animation function. 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. So the keytimes are similar to those in CSS, except that, instead of specifying them as percentages, you specify them as a fraction.
The following is the live demo for the above code. Click on the circle to start the animation.
Open this live demo on CodePen.
Note that if a list of values is used, the animation will apply the values in order over the course of the animation. If a list of values
is specified, any from
, to
and by
attribute values are ignored.
Controlling animation pace with custom easing: calcMode
, and keySplines
I’m going to go for a CSS-SMIL comparison again because the SMIL syntax and concepts will be much simpler to understand if you’re already familiar with CSS animations.
In CSS, you can choose to change the default uniform animation pace and specify a custom easing function that controls the animation, using the animation-timing-function
property. The timing function can be one of a few predefined keywords, or a cubic bézier function. The latter can be created using a tool such as this tool by Lea Verou.
In SMIL, the animation pace is specified using the calcMode
attribute. The default animation pace is linear
for all animation elements except animateMotion
(we’ll get to it later in the article). In addition to the linear
value, you can set the value to: discrete
, paced
, or spline
.
discrete
specifies that the animation function will jump from one value to the next without any interpolation. This is similar to thesteps()
function in CSS.paced
is similar tolinear
, except that it will ignore any intermediate values and progress times defined bykeyTimes
.spline
interpolates from one value in thevalues
list to the next according to a time function defined by a cubic Bézier spline. The points of the spline are defined in thekeyTimes
attribute, and the control points for each interval are defined in thekeySplines
attribute.
You’ve probably noticed the new attribute in the last sentence: the keySplines
attribute. So, what does the keySplines
attribute do?
Again, to the CSS equivalents.
In CSS, you can specify the animation pace inside every keyframe, instead of specifying one animation pace for the entire animation. This gives you better control over how each keyframe animation should proceed. An example using this feature is creating a bouncing ball effect. The keyframes for that may look like this:
@keyframes bounce {
0% {
top: 0;
animation-timing-function: ease-in;
}
15% {
top: 200px;
animation-timing-function: ease-out;
}
30% {
top: 70px;
animation-timing-function: ease-in;
}
45% {
top: 200px;
animation-timing-function: ease-out;
}
60% {
top: 120px;
animation-timing-function: ease-in;
}
75% {
top: 200px;
animation-timing-function: ease-out;
}
90% {
top: 170px;
animation-timing-function: ease-in;
}
100% {
top: 200px;
animation-timing-function: ease-out;
}
}
Instead of keyword easing functions, we could have used the corresponding cubic-bezier functions:
ease-in
=cubic-bezier(0.47, 0, 0.745, 0.715)
ease-out
=cubic-bezier(0.39, 0.575, 0.565, 1)
Let’s start by specifying the key times and list of values
for our orange circle to undergo the same bouncing effect:
<animate
xlink:href="http://css-tricks.com/#orange-circle"
attributeName="cy"
from="50"
to="250"
dur="3s"
begin="click"
values="50; 250; 120;250; 170; 250; 210; 250"
keyTimes="0; 0.15; 0.3; 0.45; 0.6; 0.75; 0.9; 1"
fill="freeze"
id="circ-anim" />
The animation will be begin on click, and will freeze once it reaches the end value. Next, in order to specify the pace of each keyframe, we’re going to add the keySplines
attribute.
The keySplines
attribute takes in set of bézier control points associated with the keyTimes
list, defining a cubic Bézier function that controls interval pacing. The attribute value is a semicolon-separated list of control point descriptions. Each control point description is a set of four values: x1 y1 x2 y2, describing the bézier control points for one time segment. The values must all be in the range 0 to 1, and the attribute is ignored unless the calcMode
is set to spline
.
Instead of taking cubic-bezier functions as values, keySplines
takes the coordinates of the two control points that are used to draw the curve. The control points can be seen in the following screenshot taken from Lea’s tool. The screenshot also shows the coordinates of each point, each colored with the same color as the point itself. For the keySplines
attribute, it is these values that we are going to use to define the pace of the keyframe animations.
SMIL allows these values to be separated either by commas with optional whitespace, or by whitespace alone. The keyTimes
values that define the associated segment are the bézier “anchor points”, and the keySplines
values are the control points. Thus, there must be one fewer set of control points than there are keyTimes
.
If we go back to the bouncing ball example, the control point coordinates for the ease-in
and ease-out
functions are shown in the following images:
So, to translate that into the SVG animation element, we get the following code:
<animate
xlink:href="http://css-tricks.com/#orange-circle"
attributeName="cy"
from="50"
to="250"
dur="3s"
begin="click"
values="50; 250; 120;250; 170; 250; 210; 250"
keyTimes="0; 0.15; 0.3; 0.45; 0.6; 0.75; 0.9; 1"
keySplines=".42 0 1 1;
0 0 .59 1;
.42 0 1 1;
0 0 .59 1;
.42 0 1 1;
0 0 .59 1;
.42 0 1 1;
0 0 .59 1;"
fill="freeze"
id="circ-anim"/>
Here’s the live demo, and make sure you run the demo in Firefox not Chrome, because there is a bug currently that prevents the demo from working properly in the latter:
Open this live demo on CodePen.
If you only want to specify an overall easing function for the entire animation without any intermediate values, you would still have to specify the keyframes using the keyTimes
attribute, but you would only specify the start and ending keyframes—namely 0; 1
—and no intermediate values
.
Additive & Accumulative Animations: additive
and accumulate
Sometimes, it’s useful to define an animation that starts from where the previous animation ended; or one that uses the accumulative sum of the previous animations as a value to proceed by. For that, SVG has two conveniently named attributes: additive
and accumulate
.
Suppose you have an element whose width you want to “grow”, or a line whose length you want to increase, or an element that you want to move step by step from one position to the other, over separate steps. This feature is particularly useful for repeated animations.
Just like any other animation, you’re going to specify from
and to
values. However, when you set additive
to sum
, each of their values is going to be relative to the original value of the animated attribute.
So, back to our circle. For our circle, the initial position of cx
is 50. When you set from="0"
to="100"
, the zero if actually the original 50, and the 100 is actually 50 + 100; in other words, it’s practically kind of like “from="50" to="150"
“.
By doing that, we get the following result:
Open this live demo on CodePen.
This is all the additive
attribute does. It just specifies whether the from
and to
values should be relative to the current value or not. The attribute only takes one of two values: sum
and replace
. The latter is the default value, and it basically means that the from
and to
values are going to replace the current/original values, which may end up causing a weird jump before the animation starts. (Try replacing sum
with replace
in the above example for a better comparison.)
However, what if we want the values to be added such that the second repetition starts off from the ending value of the previous one?
Open this live demo on CodePen.
Specifying an animation’s end time with end
In addition to specifying when an animation begins, you can also specify when it ends, using the end
attribute. For example, you can set an animation to repeat indefinitely, and then have it stop when another element starts animating. The end
attribute takes values similar to those that the begin
value takes. You can specify absolute or relative time values/offsets, repeat values, event values, etc.
For example, in the following demo, the orange circle moves slowly over a period of 30 seconds to the other side of the canvas. The green circle will also animate, but only when it’s clicked. The orange circle’s animation will end when the green circle’s animation starts—so, you want to click on the green circle to see the orange one stop.
Open this live demo on CodePen.
The same kind of animation synchronization can be achieved for two animations applied to the same element, of course. For example, suppose we set the color of the circle to animate indefinitely changing from one value to another. Then, when the element is clicked, it moves to the other side. We’ll set it now so that the color animation stops as soon as the element is clicked and the moving animation is fired.
Open this live demo on CodePen.
Defining animation intervals using multiple begin
and end
values
Indeed, both the begin
and end
attributes accept a list of semi-colon-separated values. Each value in the begin
attribute will correspond to a value in the end
attribute, thus forming active and inactive animation intervals.
You can think of this as being similar to a moving car, where the car’s tires are active and then inactive for periods of time, depending on whether or not the car is moving. You can even create the animated car effect by applying to animations to the car: one that translates the car or moves it along a path that is also an additive and accumulative animation, and another animation that rotates the car’s tires in intervals that would be synchronized with the translation.
An example specifying multiple beginning and ending times (i.e. intervals) is the following demo, where the rectangle is rotated based on the defined intervals, changing from active to inactive accordingly. (Rerun the demo if you miss the animation.)
Open this live demo on CodePen.
Note that in the above example I’ve used the <animateTransform>
element to rotate the rectangle about its center. We’ll talk about this element in more detail in an upcoming section below.
Also note that, even if you set the repeatCount
to indefinite
, it will be overridden by the end
values and will not repeat indefinitely.
Restricting the active duration of an element using min
and max
Just like you can restrict the repetition time of an animation, you can even restrict the active duration of an animation. The min
and max
attributes specify the minimum and maximum value of the active duration, respectively. They provide us with a way to control the lower and upper bound of the element active duration. Both attributes take a clock value as a value.
For min
, that specifies the length of the minimum value of the active duration, measured in element active time. Value must be greater than or equal to 0, which is the default value and does not constrain the active duration at all.
For max
, the clock value specifies the length of the maximum value of the active duration, measured in element active time. Value must also be greater than 0. The default value for max
is indefinite
. This does not constrain the active duration at all.
If both min
and max
attributes are specified then the max
value must be greater than or equal to the min
value. If this requirement is not fulfilled then both attributes are ignored.
But what defines the active duration of an element? We mentioned the repeat duration before, in addition to the “simple duration”, which is the duration of the animation without any repetition (specified using dur
), so how do all of these work together? Which overrides what? and then what about the end
attribute which would override and simply end the animation?
The way it happens is that the browser will first compute the active duration based on the dur
, repeatCount
, repeatDur
, and end
values. Then, it runs the computed duration against the specified min
and max
values. If the result is within the bounds, this first computed duration value is correct and will not be changed. Otherwise two situations may occur:
- If the first computed duration is greater than the
max
value, the active duration of the element is defined to be equal to themax
value. - If the first computed duration is less than the
min
value, the active duration of the element becomes equal to themin
value and the behavior of the element is as follows:- If the repeating duration (or the simple duration if the element doesn’t repeat) of the element is greater than
min
then the element is played normally for the (min
constrained) active duration. - Otherwise the element is played normally for its repeating duration (or simple duration if the element does not repeat) and then is frozen or not shown depending on the value of the
fill
attribute.
- If the repeating duration (or the simple duration if the element doesn’t repeat) of the element is greater than
That leaves us with how the browser actually computes the active duration. For sake of brevity, I’m not going to get into the details here. But there is a very comprehensive table in the specification that shows the different combinations of the dur
, repeatCount
, repeatDur
, and end
attributes, and then shows what the active duration will be based on each combination. You can check the table out and read more about this in this section of the specification.
Lastly, if an element is defined to begin before its parent (e.g. with a simple negative offset value), the minimum duration is measured from the calculated begin time not the observed begin. This means that the min
value may have no observed effect.
<animate>
example: morphing paths
One of the attributes that can be animated in SMIL (but not in CSS) is the d
attribute (short for data) of an SVG <path>
. The d
attribute contains the data which defines the outline of the shape that you’re drawing. The path data consists of a set of commands and coordinates that tell the browser where and how to draw points, arcs, and lines that make up the final path.
Animating this attribute allows us to morph SVG paths and create shape tweening effects. But, in order to be able to morph shapes, the start, end, and any intermediate path shapes need to have the exact same number of vertices/points, and they need to appear in the same order. If the number of vertices doesn’t match, the animation wouldn’t work. The reason for this is that the shape changing actually happens by moving the vertices, and interpolating their positions, so if one vertex is missing or does not match, the paths won’t be interpolated anymore.
To animate an SVG path, you specify the attributeName
to be d
, and then set the from
and to
values that specify the start and end shapes, and you can use the values
attribute to specify any intermediate values you want the shape to go through in between.
For the sake of brevity, I won’t get into the details of how to do this here. Instead, you can read this excellent article by Noah Blon, in which he explains how he created a shape-tweening kind-of-loading animation using <animate>
. The live demo for Noah’s article is this:
Open this live demo on CodePen.
And here’s another morphing example by Felix Hornoiu:
See the Pen SVG Countdown by Felix Hornoiu (@felixhornoiu) on CodePen.
You can even morph the values of a path being used as a clipping mask! An example of that by Heather Buchel:
See the Pen Loading Animation with Morphing SVG! by Heather Buchel (@hbuchel) on CodePen.
Animating along arbitrary paths: The <animateMotion>
Element
The <animateMotion>
element is my favorite SMIL animation element. You can use it to move an element along a path. You specify the motion path using one of two ways which we’re going to cover next, and then to set the element up so that is moves along that path.
The <animateMotion>
element accepts the same attributes mentioned earlier, plus three more: keyPoints
, rotate
, and path
. Also, there is one difference regarding the calcMode
attribute, where the default value is paced
for <animateMotion>
, not linear
.
Specifying the motion path using the path
attribute
The path
attribute is used to specify the motion path. It is expressed in the same format and interpreted the same way as the d
attribute on the path
element. The effect of a motion path animation is to add a supplemental transformation matrix onto the current transformation matrix for the referenced object which causes a translation along the x- and y-axes of the current user coordinate system by the computed X and Y values computed over time. In other words, the path specified is calculated relative to the element’s current position, by using the path data to transform the element onto the path position.
For our circle, we’re going to animate it along a path that looks like the following:
The code required for the circle to move along this path is:
<animateMotion
xlink:href="http://css-tricks.com/#circle"
dur="1s"
begin="click"
fill="freeze"
path="M0,0c3.2-3.4,18.4-0.6,23.4-0.6c5.7,0.1,10.8,0.9,16.3,2.3 c13.5,3.5,26.1,9.6,38.5,16.2c12.3,6.5,21.3,16.8,31.9,25.4c10.8,8.7,21,18.3,31.7,26.9c9.3,7.4,20.9,11.5,31.4,16.7
c13.7,6.8,26.8,9.7,41.8,9c21.4-1,40.8-3.7,61.3-10.4c10.9-3.5,18.9-11.3,28.5-17.8c5.4-3.7,10.4-6.7,14.8-11.5
c1.9-2.1,3.7-5.5,6.5-6.5" />
There is one thing I want to focus on here: the coordinates in the path data. The path starts by moving (M) to the point with coordinates (0, 0), before it starts to draw a curve (c) to another point. It is important to note that the (0, 0) point is actually the position of the circle, no matter where it is – NOT the top left corner of the coordinate system. As we mentioned above, the coordinates in the path
attribute are relative to the current position of the element!
The result of the above code is:
Open this live demo on CodePen.
If you were to specify the path starting from a point other than (0, 0), the circle would abruptly jump by the amount specified in the beginning point. For example, suppose you draw a path in Illustrator and then export that path data to use as a motion path (that’s what I did the first time I did this); the exported path may look something like this:
<path fill="none" stroke="#000000" stroke-miterlimit="10" d="M100.4,102.2c3.2-3.4,18.4-0.6,23.4-0.6c5.7,0.1,10.8,0.9,16.3,2.3
c13.5,3.5,26.1,9.6,38.5,16.2c12.3,6.5,21.3,16.8,31.9,25.4c10.8,8.7,21,18.3,31.7,26.9c9.3,7.4,20.9,11.5,31.4,16.7
c13.7,6.8,26.8,9.7,41.8,9c21.4-1,40.8-3.7,61.3-10.4c10.9-3.5,18.9-11.3,28.5-17.8c5.4-3.7,10.4-6.7,14.8-11.5
c1.9-2.1,3.7-5.5,6.5-6.5"/>
The starting point of the path in this case is (100.4, 102.2). If we were to use this data as the motion path, the circle will jump by ~100 units to the right and ~102 units downwards, and then start the motion along the path relative to the new position. So, make sure to keep this in mind when you prepare the motion path for your animation.
If used, attributes from
, by
, to
and values
specify a shape on the current canvas which represents the motion path.
Specifying the motion path using the <mpath>
element
There is also another way you can specify a motion path. Instead of using the relative path
attribute, you can reference an external path using the <mpath>
element. The <mpath>
, a child of the <animateMotion>
element, would then reference the external path using the xlink:href
attribute.
<animateMotion xlink:href="http://css-tricks.com/#circle" dur="1s" begin="click" fill="freeze">
<mpath xlink:href="http://css-tricks.com/#motionPath" />
</animateMotion>
The motion path <path>
can be defined anywhere in the document; it can even be literally just defined inside a <defs>
element and not rendered on the canvas at all. In the next example, the path is rendered because, in most cases, you may want to show the path that the element is moving along.
Note that, according to the specification:
The various (x, y) points of the shape provide a supplemental transformation matrix onto the CTM for the referenced object which causes a translation along the x- and y-axes of the current user coordinate system by the (x,y) values of the shape computed over time. Thus, the referenced object is translated over time by the offset of the motion path relative to the origin of the current user coordinate system. The supplemental transformation is applied on top of any transformations due to the target element’s
transform
property or any animations on that attribute due toanimateTransform
elements on the target element.
Again, the position of the circle is “multiplied” or “transformed” by the coordinates in the path data.
In the next example, we have a path in the middle of the canvas. The circle is positioned at the beginning of the path. Yet, when the motion path is applied, the circle does not start its motion from its current position. See the demo for a better explanation. Click on the circle to animate it.
Open this live demo on CodePen.
See how the circle does follow the same shape of the path, but over a different position? This is due to the fact that the circle’s position is transformed by the values of the path data.
One way around this is to start with the circle being positioned at (0, 0), so that when the path data is used to transform it, it will start and proceed as expected.
Another way is to apply a transformation that “resets” the coordinates of the circle so that they compute to zero before the path is applied.
The following is a modified version of the above demo, using a closed path and repeating the motion animation indefinitely.
Open this live demo on CodePen.
Override Rules for <animateMotion>
Since there are more than one way to do the same thing for animateMotion
, it only makes sense to have override rules to specify which values override others.
The override rules for animateMotion
are as follows:
- Regarding the definition of the motion path, the
mpath
element overrides the thepath
attribute, which overridesvalues
, which overridesfrom
,by
andto
. - Regarding determining the points which correspond to the
keyTimes
attributes, thekeyPoints
attribute overridespath
, which overridesvalues
, which overridesfrom
,by
andto
.
Setting an element’s orientation along a motion path with rotate
In our previous example, the element we were animating along the path happened to be a circle. But what if we’re animating an element that has a certain orientation like, say for example, a car icon? The car icon in the following example is designed by Freepik.
In this example, I’ve replaced the circle with a group with an ID of “car”, which contains the element making up the group. Then, in order to avoid the problem with the motion along the path mentioned above, I’ve applied a transformation to the car to that translates it by a specific amount, so that the initial position ends up at (0, 0). The values inside the transformations are actually the coordinates of the point where the first path of the car starts drawing (right after the move command M).
The car then starts moving along the motion path. But… this is how the motion looks like:
Open this live demo on CodePen.
The car’s orientation is fixed, and does not change to match that of the motion path. In order to change that, we’re going to use the rotate
attribute.
The rotate
attribute takes one of three values:
auto
: Indicates that the object is rotated over time by the angle of the direction (i.e., directional tangent vector) of the motion path.auto-reverse
: Indicates that the object is rotated over time by the angle of the direction (i.e., directional tangent vector) of the motion path plus 180 degrees.- a number: Indicates that the target element has a constant rotation transformation applied to it, where the rotation angle is the specified number of degrees.
To fix the orientation of the car in the above example, we’ll start with setting the rotation value to auto
. We’ll end up with the following result:
Open this live demo on CodePen.
If you want the car to move outside the path, the auto-reverse
value fixes that.
Open this live demo on CodePen.
This looks better, but we still have one problem: the car looks like it’s moving backwards along the path! In order to change that, we’d need to flip the car along its y-axis. This can be done by scaling it by a factor of “-1” along that axis. So, if we apply the transformation to the g
with the car
ID, the car will move forward as expected. The scaling transformation is just going to be chained with the previous translation we applied earlier.
<g id="car" transform="scale (-1, 1) translate(-234.4, -182.8)">
And the final demo looks like this:
Open this live demo on CodePen.
Controlling the animation distance along the motion path with keyPoints
The keyPoints
attribute provides the ability to specify the progress along the motion path for each of the keyTimes
specified values. If specified, keyPoints
causes keyTimes
to apply to the values in keyPoints
rather than the points specified in the values
attribute array or the points on the path
attribute.
keyPoints
takes a semicolon-separated list of floating point values between 0 and 1 and indicates how far along the motion path the object shall move at the moment in time specified by corresponding keyTimes
value. Distance calculations are determined by the browser’s algorithms. Each progress value in the list corresponds to a value in the keyTimes
attribute list. If a list of keyPoints
is specified, there must be exactly as many values in the keyPoints
list as in the keyTimes
list.
The keyPoints
attribute doesn’t seem to work in Chrome and Firefox at the time of writing of this article. It’s probably not implemented yet, as are other parts of the spec mentioned before.
Moving text along an arbitrary path
Moving text along an arbitrary path is different from moving other SVG elements along paths. To animate text, you’re going to have to use the <animate>
element, not the <animateMotion>
element.
First, let’s start by positioning the text along a path. This can be done by nesting a <textPath>
element inside the <text>
element. The text that is going to be positioned along a path will be defined inside the <textPath>
element, not as a child of the <text>
element.
The textPath
is then going to reference the actual path that we want to use, just like we did in the previous examples. The referenced path can also be either rendered on the canvas, or defined inside a <defs>
. Check the code in the following demo out.
Open this live demo on CodePen.
To animate the text along that path, we’re going to use the <animate>
element to animate the startOffset
attribute.
The startOffset
represents the offset of the text on the path. 0% is the beginning of the path; 100% represents the end of it. So if, for example, the offset is set to 50%, the text will start halfway through the path. I think you can see where we’re going from here.
By animating the startOffset
, we’re going to create the effect of text moving along the path. Check the code in the following demo out.
Open this live demo on CodePen.
Animating transformations: The <animateTransform>
Element
The <animateTransform>
element animates a transformation attribute on a target element, thereby allowing animations to control translation, scaling, rotation and/or skewing. It takes the same attributes mentioned for the <animate>
element, plus an additional attribute: type
.
The type
attribute is used to specify the type of the transformation that’s being animated. It takes one of five values: translate
, scale
, rotate
, skewX
, and skewY
.
The from
, by
and to
attributes take a value expressed using the same syntax that is available for the given transformation type:
+ For a type="translate"
, each individual value is expresse