oak.is thinking

Animated SVGs: Custom easing and timing

The chart above is an animated SVG featured on Sprout.

This chart, and one other animation on Sprout, were initially GIFs. By using animated SVGs instead of GIFs we were able to reduce our page size from 1.6 mb to 389 kb, and reduce our page load time from 8.75 s to 412 ms. That’s a huge difference.

Below, I’ll break down the animation of one of the circles seen in the chart. The technique applies to all of the elements in the graphic. With this you can create your own lightweight animated graphic.

Open your shape

If you created your shape with a vector program, it’ll likely be self-closing:

<circle />

Open it up and include an animateˆ element inside:

<circle>
    <animate />
</circle>

Our circle looks like this. Each of the properties I walk through below apply within the animate tag:

<circle fill="#FFFFFF" stroke="#B450FF" stroke-width="6" stroke-miterlimit="10" cx="153" cy="127" r="6">
    <animate

Select an attribute

The attributeNameˆ is the attribute that we’ve chosen to animate. This could be opacity, stroke-width, width, or a number of others. We’ve chosen to use cy, it refers to the y position at the center of our circle.

        attributeName="cy"

From and To positions

Our from and toˆ properties tell the shape where to start and end (in terms of the attribute we’re animating). I want this animation to loop seamlessly so the from and to are the same value. (Note: These are the same as the cy in my circle element!)

        from="127"
        to="127"

Set a Begin time and Duration

We can set an initial delay for the animation with the beginˆ property. Ain’t nobody got time for that so I set it to 0. The overall duration of the animation is set with the durˆ property.

        begin="0s"
        dur="4s"

Setting Values

These valuesˆ refer to the attribute that we’re animating. Because we set our attributeName to cy above, these are the cy positions. There are 3 positions this circle will take in the animation (at 127, 117, and 90 units). We repeat those values so the circle will pause (though really it’s just animating to the same position). The 7th value completes the last pause and ends our loop.

        values="127;117;117;90;90;127;127"

Notice that the last values match the first value, and that those match our from and to values.

Aside: Using duplicate values is one method for inserting a pause in an animation. Another, probably more proper method, is to create separate animate elements for each of the movements and daisychain them together with ids, .endˆ, and an offset value. I used the duplicate values instead to reduce the number of elements in my SVG.

Easing the transition

The keySplinesˆ describe the easing method, just like the cubic Bézier easing method in CSS transitions.

        keySplines="
            0.1 0.8 0.2 1; 
            0.1 0.8 0.2 1; 
            0.1 0.8 0.2 1; 
            0.1 0.8 0.2 1; 
            0.1 0.8 0.2 1; 
            0.1 0.8 0.2 1" 

The 4 values on each line are coordinates for the handles describing the easing curve. I find it easiest to first visualize this with a vector drawing:

Without easing our animation looks mechanical. With easing can simulate momentum and breathe life into it.

Aside: The spec for keySplinesˆ requires these values to be between 0 and 1. Initially we had a bounce effect (below) (with values 0.4 1.6 0.8 0.8) but the effect has an out-of-range value. While this works beautifully in Webkit, the animation breaks in Firefox. (The overshoot and turnback in the resulting Bézier curve are what give the circle its bounce)

Because we have 6 transitions, 3 times that the circle moves to a new position and 3 times that it moves to its current position (the pause), we list 6 keySplines, separated by semicolons. You can use different values for each transition too.

Frames

Our keyTimesˆ set the pace of our keyframes.

        keyTimes="0;0.22;0.33;0.55;0.66;0.88;1" 

These times coordinate with our values. I wanted the movements to take twice as long as the pauses so I gave about 0.22 units to the movements and about 0.11 units to the pauses. Putting that together with the values it looks like:

With even pacing the circle pauses and moves for equal amounts of time.

With our custom pacing I offset the pausing and moving to give the movement more time to play out. These shorter pauses make the animation feel snappy in the right places.

calcMode

The calcModeˆ attribute tells the animation how to transition between values. The spline values refers to the cubic Bézier easing method we wrote above.

        calcMode="spline"

Looping and ending

We set our repeatCountˆ to indefinite so that our animation loops.

        repeatCount="indefinite"
    />
</circle>

Finally, we close our animate element and our circle element.

Putting it all together

Putting it all together we get:

<circle fill="#FFFFFF" stroke="#B450FF" stroke-width="6" stroke-miterlimit="10" cx="153" cy="127" r="6">
    <animate
        attributeName="cy"
        from="127"
        to="127"
        begin="0s"
        dur="4s"
        values="127;117;117;90;90;127;127"
        keySplines="
            0.1 0.8 0.2 1; 
            0.1 0.8 0.2 1; 
            0.1 0.8 0.2 1; 
            0.1 0.8 0.2 1; 
            0.1 0.8 0.2 1; 
            0.1 0.8 0.2 1" 
        keyTimes="
            0;0.22;0.33;0.55;0.66;0.88;1" 
        calcMode="spline"
        repeatCount="indefinite"
    />
</circle>

Outro

All elements in the chart use this same method. This technique works for the lines also. With the circles we’re animating the cy position. With each of the lines we animated the y1 and y2 positions (two separate animate elements).

We chose to use animated SVGs instead of embedded videos or animated GIFs to reduce page size and load time. Our final chart animation weighed in at just 15 kb. There are JavaScript libraries available for animating SVGs but we avoided them because they aren’t as light or agile as a pure SVG animation.

Writing our animation by hand ultimately gave us a better understanding of how they work.

Additional resources: