Creative Coding with SVGs
zuubaDigital

SMIL Animation

SMIL pt.1 practice page

What is SMIL?

SMIL is an acronym for Synchronized Multimedia Integration Language. SMIL animation involves the use of animation elements inside the SVG. There are three types of animation elements:

<animate>

<animateTransform>

<animateMotion>

While not quite as powerful as Javascript, SMIL elements provide a declarative way to chain and sequence complex animations. They also allow you to avoid the inconsistencies that you often see with CSS transform animations.

Unlike CSS and Javascript, which can be used to animate both HTML and SVG DOM elements, SMIL animations can only be used with SVGs.

animate

The <animate> element is used when you need to animate numeric attributes of an element, such as width, height, cx, cy, x, y, and fill.

We’ll start with something familiar. Again, we’ll try to animate the circle from the top to the bottom of the svg using SMIL animations.

We start by adding an animate tag inside of the circle element.

1 2 3 4 5 <svg width="200" height="300" viewBox="0 0 200 300"> <circle cx="100" cy="20" r="10" fill="green"> <animate/> </circle> </svg>

attributeName

We’ll add the name of the attribute that we want to animate - cy.

1 2 3 4 5 6 <svg width="200" height="300" viewBox="0 0 200 300"> <circle cx="100" cy="20" r="10" fill="green"> <animate attributeName="cy" /> </circle> </svg>

from, to, dur

Next we’ll set the beginning and end values using the from and to attributes, and the duration (dur) of the animation.

1 2 3 4 5 6 7 8 9 <svg width="200" height="300" viewBox="0 0 200 300"> <circle cx="100" cy="20" r="10" fill="green"> <animate attributeName="cy" from="20" to="230" dur="2s" /> </circle> </svg>

values

Instead of using from and to/by to designate the beginning and end of the animation, we can use the values attribute, with the individual values separated by a semicolon. This is very useful when animating to several positions, instead of just two.

values="<value 1>; <value 2>;..."
1 2 3 4 5 6 7 8 <svg width="200" height="300" viewBox="0 0 200 300"> <circle cx="100" cy="20" r="10" fill="green"> <animate attributeName="cy" values="20; 230" dur="2s" /> </circle> </svg>

repeatCount

The repeatCount attribute is used to specify the number of times the animation runs. Like with CSS animation-iteration-count, it can be a number, or it can be “indefinite” (infinite).

repeatCount="<number> | indefinite"
1 2 3 4 5 6 7 8 9 10 <svg width="200" height="300" viewBox="0 0 200 300"> <circle cx="100" cy="20" r="10" fill="green"> <animate attributeName="cy" from="20" to="230" dur="2s" repeatCount="indefinite" /> </circle> </svg>

fill

To prevent the circle from popping back to its original state, we’ll need to set the fill attribute of the animate element to freeze, which is similar to CSS animation-fill-mode: forward, in that it defines the final state of the animation.

fill="freeze | remove (default)"
1 2 3 4 5 6 7 8 9 10 11 <svg width="200" height="300" viewBox="0 0 200 300"> <circle cx="100" cy="20" r="10" fill="green"> <animate attributeName="cy" from="20" to="230" dur="2s" repeatCount="3" fill="freeze" /> </circle> </svg>

begin

To delay the animation we can use the begin attribute and pass it a time value:

begin="<time in seconds> | click"
1 2 3 4 5 6 7 8 9 10 11 12 <svg width="200" height="300" viewBox="0 0 200 300"> <circle cx="100" cy="20" r="10" fill="green"> <animate attributeName="cy" from="20" to="230" dur="2s" repeatCount="3" fill="freeze" begin="2s"/> </circle> </svg>

Or we can set the begin to a when the user clicks the circle:

1 2 3 4 5 6 7 8 9 10 11 12 <svg width="200" height="300" viewBox="0 0 200 300"> <circle cx="100" cy="20" r="10" fill="green"> <animate attributeName="cy" from="20" to="230" dur="2s" repeatCount="3" fill="freeze" begin="click"/> </circle> </svg>
click circle to begin

end

To stop the animation after a set period of time we can use the end attribute. In the example below we'll set the repeatCount to indefinite, but then we'll also set an end time.

end="<time in seconds> | click"
1 2 3 4 5 6 7 8 9 10 11 12 <svg width="200" height="300" viewBox="0 0 200 300"> <circle cx="100" cy="20" r="10" fill="green"> <animate attributeName="cy" from="20" to="230" dur="1s" repeatCount="indefinite" fill="freeze" end="3.5s"/> </circle> </svg>

Just like the begin attribute, we can end the animation when the circle is clicked. (I've slowed down the animtion to make the circle easier to click!)

1 2 3 4 5 6 7 8 9 10 11 12 <svg width="200" height="300" viewBox="0 0 200 300"> <circle cx="100" cy="20" r="10" fill="green"> <animate attributeName="cy" from="20" to="230" dur="10s" repeatCount="indefinite" fill="freeze" end="click"/> </circle> </svg>

href

You've probably noticed that all of the animate elements in the examples have been "encapsulated" by the element we wish to animate.

<shape-element>
  <animate .../>
</shape-element>

If you want to keep them separate, all you have to do is pass the id of the element you wish to animate to the animate element via the href attribute

<circle id="my-circle" ... />

<animate href="#my-circle" .../>

easing

Now for something a bit more complex. How do we add easing to the animation? Unfortunately we don’t have pre-defined easing functions you find for CSS transition-timing-function or animation-timing-function.

To add easing, we’ll need to use three more attributes:

calcMode, keyTimes, and keySplines

calcMode

In order to use cubic-bezier curves within an animation, calcMode has to be set to “spline”. (The default value is linear)

calcMode="spline | remove (default)"
1 2 3 4 5 6 7 8 9 10 11 12 13 <svg width="200" height="300" viewBox="0 0 200 300"> <circle ...> <animate attributeName="cy" values="20; 280" dur="2s" calcMode="spline" keyTimes="0; 1" keySplines="0 0 .5 1" repeatCount="indefinite" /> </circle> </svg>

keyTimes

The keyTimes attribute allows you to set the pacing of the animation. You need to have the same number of keyTimes as values. Each keytime is a value from 0 to 1, and represents the point in time when each value is reached. In this case it means that cy should be 20 at keytime 0 (the beginning of the animation) and 280 at keytime 1 (the end).

values="<value 1>; <value 2>; <value 3>; ..."
...
keyTimes="<time 1>; <time 2>; <time 3>; ..."
1 2 3 4 5 6 7 8 9 10 11 12 <svg width="200" height="300" viewBox="0 0 200 300"> <circle ...> <animate attributeName="cy" values="20; 280" dur="2s" calcMode="spline" keyTimes="0; 1" repeatCount="indefinite" /> </circle> </svg>

It’s easier to see the effect of keyTimes with multiple animation values. In this example, the cy is 20 at the 0 keytime, it’s 250 at the .5 keyTime (50% of the duration) and it’s 280 at the 1 keyTime (100% of the duration)

1 2 3 4 5 6 7 8 9 10 11 12 <svg width="200" height="300" viewBox="0 0 200 300"> <circle ...> <animate attributeName="cy" values="20;250;280" dur="2s" calcMode="spline" keyTimes="0; .5; 1" repeatCount="indefinite" /> </circle> </svg>

keySplines

The keySplines are the cubic bezier curves that define the motion, separated by a semicolon. In the example below, the first keySpline (0.5 0 0.5 1) describes the cubic bezier curve between the 1st and 2nd values (50 and 150) and the second keySpline (also 0.5 0 0.5 1) describes the cubic bezier curve between the 2nd and 3rd values (150 and 280). You will always have ONE LESS keySpline than values.

values="<value 1>; <value 2>; <value 3>; ..."
...
keySplines="<bezier curve 1 to 2>; <bezier curve 2 to 3>;..."
1 2 3 4 5 6 7 8 9 10 11 12 13 <svg width="200" height="300" viewBox="0 0 200 300"> <circle cx="100" cy="20" r="10" fill="green"> <animate attributeName="cy" values="20; 150; 280" dur="2s" calcMode="spline" keyTimes="0; 0.5; 1" keySplines="0.5 0 0.5 1; 0.5 0 0.5 1" repeatCount="indefinite" /> </circle> </svg>

You can define your own bezier curves at https://cubic-bezier.com/

simultaneous animations

We can define multiple simultaneous animations for the same element. Here we’ll animate cx, radius, and fill color.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <circle cx="50" cy="250" r="20" fill="#93C17E"> <animate attributeName="cx" values="50; 450; 250" dur="2s" begin="0" fill="freeze" /> <animate attributeName="r" values="20; 100" dur="2s" fill="freeze" /> <animate attributeName="fill" values="#93C17E; #ff00ff; #009900" dur="4s" fill="freeze" /> </circle>

sequential animations

We can also make the animations run sequentially by using the “begin” attribute and setting it to run at either the beginning or end of another animation. In order to do so we'll need to give each animate element an id:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <circle cx="50" cy="250" r="20" fill="#93C17E"> <animate id="slide" attributeName="cx" values="50; 450; 250" dur="2s" begin="0" fill="freeze" /> <animate id="grow" attributeName="r" values="20; 100" dur="2s" fill="freeze" /> <animate id="change" attributeName="fill" values="#93C17E; #ff00ff; #009900" dur="4s" fill="freeze" /> </circle>

To set an animation to run in coordination with another animation, you use the id of that other animation followed by a "." and either begin or end. The value can include a time offset as well.

begin="<animation name>.< begin | end> + <time offset>"

For example, I can set an animation to run 3 seconds after another animation ends as follows.

begin="some_animation.end + 3s"

So to get the "grow" animation to run after the "slide" animation, you'd use the following begin attribute in the "grow" animation:

begin="slide.end"

And to get the "change" animation to run half a second after the start of the "grow" animation, you'd use the following begin attribute in the "change" animation:

begin="grow.begin + 500ms"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <svg width="500" height="500" viewBox="0 0 500 500"> <circle cx="50" cy="250" r="20" fill="#93C17E"> <animate id="slide" attributeName="cx" values="50; 450; 250" dur="2s" begin="0" fill="freeze" /> <animate id="grow" attributeName="r" values="20; 100" dur="2s" fill="freeze" begin="slide.end" /> <animate attributeName="fill" values="#93C17E; #ff00ff; #009900" dur="4s" fill="freeze" begin="grow.begin + 500ms" /> </circle> </svg>

animateTransform

The animateTransform element is used only when you need to animate transformation attributes:

  • translate,
  • rotate,
  • scale
  • skewX, or skewY.

Otherwise the functionality of <animateTransform> is identical to <animate>. All the attributes - from, to, dur, fill, calcMode, etc. - have the same functionality.

The only difference in the syntax of animateTransform from animate is that animateTransform has a type attribute, where you designate the type of transform animation to be performed.

type="translate | rotate | scale | skewX | skewY"

rotate

In this example, we’re animating a rectangles transform-rotate value from 0° to 360°. The first number in both the from and to attributes represents the angle, and the second and third represent the x and y position around which the rotation occurs.

from="<angle> <x> <y>"
to="<angle> <x> <y>"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <svg width="500" height="500" viewBox="0 0 500 500"> <rect x="150" y="150" rx="10" ry="10" width="200" height="200" fill="skyblue"> <animateTransform attributeName="transform" type="rotate" from="0 250 250" to="360 250 250" begin="0s" dur="3s" repeatCount="indefinite" ></animateTransform> </rect> </svg>

If you set the transform-origin presentation attribute to the point you want the rotation to occur around, you can dispense with the x and y values in the from and to attributes.

1 2 3 4 5 6 7 8 9 10 11 12 13 <svg width="500" height="500" viewBox="0 0 500 500"> <rect transform-origin="250 250" ...> <animateTransform attributeName="transform" type="rotate" from="0" to="360" begin="0s" dur="3s" repeatCount="indefinite" ></animateTransform> </rect> </svg>

For multiple rotations we can use the values attribute, with the angle values separated by a semicolon.

1 2 3 4 5 6 7 8 9 10 11 12 <svg width="500" height="500" viewBox="0 0 500 500"> <rect transform-origin="250 250" ...> <animateTransform attributeName="transform" type="rotate" values="0; 360; 180; 360" begin="0s" dur="3s" repeatCount="indefinite" ></animateTransform> </rect> </svg>

Note that the rotations can have different center points. Look what happens when we get rid of the transform-origin attribute and instead include a changing rotation point in the values attribute.

1 2 3 4 5 6 7 8 9 10 11 12 <svg width="500" height="500" viewBox="0 0 500 500"> <rect x="150" y="150" rx="10" ry="10" width="200" height="200" fill="red"> <animateTransform attributeName="transform" type="rotate" values="0 250 250; 360 250 100; 180 250 400; 360 250 250" begin="0s" dur="3s" repeatCount="indefinite" ></animateTransform> </rect> </svg>

scale

For the animateTransform scale type, if you want to scale the object from it's center, you have to make sure to set it's transform-origin attribute, as I've done below:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <svg width="500" height="500" viewBox="0 0 500 500"> <rect x="150" y="150" rx="10" ry="10" width="200" height="200" fill="blue" transform-origin="250 250"> <animateTransform attributeName="transform" type="scale" values="1; 2; 1" begin="0s" dur="3s" repeatCount="indefinite" ></animateTransform> </rect> </svg>

skewX | skewY

Like the scale type, we can set the transform-origin property to control how the object skews during the animation. In the example below the transform-origin point is set at the upper left-hand corner of the square.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <svg width="500" height="500" viewBox="0 0 500 500"> <rect x="150" y="150" rx="10" ry="10" width="200" height="200" fill="orange" transform-origin="150 150" > <animateTransform attributeName="transform" type="skewX" values="0; 30; 0" begin="0s" dur="3s" repeatCount="indefinite" ></animateTransform> </rect> </svg>

additive animations

What if you want to perform multiple transform animations at the same time? For example, what if we want the square to both scale and rotate? Let's try to do so by adding multiple animateTransforms, one for scale and one for rotate.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <svg width="500" height="500" viewBox="0 0 500 500"> <rect x="200" y="200" rx="10" ry="10" width="100" height="100" fill="purple" transform-origin="250 250"> <animateTransform attributeName="transform" type="scale" values="1;3;1" begin="0s" dur="3s" repeatCount="indefinite" ></animateTransform> <animateTransform attributeName="transform" type="rotate" values="0;360;0" begin="0s" dur="3s" repeatCount="indefinite" ></animateTransform> </rect> </svg>

As you can see, only the rotate animation works. By default, animations to a particular property replace previous animations to that same property. Since both animateTransforms are animating the transform type, the second one (rotate) overwrites the first (scale).

To avoid this, we'll need to use the additive property, which allows you to combine animations to the same property.

additive = "replace" | "sum"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <svg width="500" height="500" viewBox="0 0 500 500"> <rect x="200" y="200" rx="10" ry="10" width="100" height="100" fill="purple" transform-origin="250 250"> <animateTransform attributeName="transform" type="scale" values="1;3;1" begin="0s" dur="3s" repeatCount="indefinite" additive="sum" ></animateTransform> <animateTransform attributeName="transform" type="rotate" values="0;360;0" begin="0s" dur="3s" repeatCount="indefinite" additive="sum" ></animateTransform> </rect> </svg>