Dave Lovemartin

Animating SVGs

How SVGs work and how to animate them

last updated: 18.02.2024

When I started to learn to code websites, there were a few different options for creating icons. You could use a sprite sheet, or use a special icon font but I always liked SVGs.

After all they are scalable, so they can be created with a small footprint. But they can also be blown up to fill any large screen without loss of definition. And as a subset of XML, they can be written inline in HTML and targeted with CSS.

Whilst the other techniques have fallen out of fashion, SVGs are here to stay.

You can create SVGs in your favourite Vector Graphic editing software, and export the code created and plonk it into your code. You can use the excellent SVGOMG tool, to optimise the code as your favourite Vector Graphic software may add some extra bloat.

You can also import SVGs as JSX, in React (although you may not want to do that).

I thought it would be interesting to get under the hood and explore how to create SVGs from scratch myself rather than solely using software.

This is a work-in-progress where I intend to uncover the basics of SVG animation and then do some follow up experiments where I animate elements of my personal website and explore generative SVGs.

The SVG element

In HTML, there is a SVG element which you can use to render a SVG document fragment. The SVG element has a viewBox attribute which defines the coordinates of your viewport. The window into your SVG world.

The first two numbers in the viewBox denote the top and left co-ordinates of the viewport and the last two numbers represent the width and length of the viewport.viewport.

<svg viewBox="0 0 100 100" />
01020304050607080901000102030405060708090100

Everything drawn in a SVG is written using co-ordinates that are relative to its viewBox which scales to the dimensions of its width and height.

Shape elements

You can add shape elements as children of an SVG. They are circle, ellipse, line, polygon, polyline, rect and path.

I’m sure you can take a reasonable guess at what these shapes are. The most flexible and interesting one is the path element.

The path element is defined along with a path data property, d. The path data is a “series of commands” that tell the browser how the path should be drawn — in a similar way to how you might program a robot turtle.

Each command starts with a letter and, in most cases, followed by some co-ordinates.

Here, I use the ”move” command (M), along with the ”line” command (L) and the ”close-path” command (Z) to draw a polygon.

<svg viewBox="0 0 100 100">
  <path d="M 0 40 L 50 0 L 100 60 L 30 70 Z" fill="none" stroke="#2c363f"></path>
</svg>
01020304050607080901000102030405060708090100

The command code can either be uppercase or lowercase:

Our commands instruct a cursor (our turtle) to determine where to start and stop drawing and what sort of lines to draw.

Arcs and Bézier Curves

To draw curves, SVG gives us a few more commands:

The arc command lets us draw a section of an ellipse. If we write the command as a function, it takes the following parameters: a(radius x, radius y, x-axis rotation, large-arc-flag, sweep-flag, x, y).

Given the co-ordinates, there are four ways an arc can be drawn, so the flags determine which of these to use.

<svg
  fill="none"
  stroke-width="2"
>
  <path stroke="#00487d"
    d="M 40 40 A 25 25 0 1 0 60 60"
  ></path>
  <path stroke="red"
    d="M 40 40 A 25 25 0 1 1 60 60"
  ></path>
  <path stroke="green"
    d="M 40 40 A 25 25 0 0 1 60 60"
  ></path>
  <path stroke="blue"
    d="M 40 40 A 25 25 0 0 0 60 60"
  ></path>
</svg>
01020304050607080901000102030405060708090100

Bézier curves are a line from one point to another controlled by control points, quadratic bézier curves have one control point. A control point curves the line towards it, as if the control point was pulling it in its direction.

The Q command takes four arguments: the X and Y co-ordinates of the control point, and the X and Y co-ordinates of the end point.

<svg
  fill="none"
  stroke-width="2"
>
  <path stroke="#00487d"
    d="M 10 10 L 10 30 Q 10 90 70 90 L 90 90"
  ></path>
</svg>
01020304050607080901000102030405060708090100

A T command will draw a quadratic bézier curve using the reflection of the previous curve’s control point.

<svg
  fill="none"
  stroke-width="2"
>
  <path stroke="#00487d"
      d="M 10 10 Q 10 50 50 50 T 90 90"
  ></path>
</svg>
01020304050607080901000102030405060708090100

Cubic bézier curves have two control points and they can written with the C command and reflected with the S command:

<svg
  fill="none"
  stroke-width="2"
>
  <path stroke="#00487d"
      d="M 50 10 C 10 60 10 70 50 70 S 100 0 50 10"
  ></path>
</svg>
01020304050607080901000102030405060708090100

Transform attribute

Once we have our shapes, we can transform them. SVGs give us the following transformations:

(There’s also something called matrix which allows you do all of the above).

When we transform a shape, we create a copy of the co-ordinate system of the viewport and transform the whole lot.

Let’s see this in action:

01020304050607080901000102030405060708090100
<path
    stroke="#00487d"
    fill="#ffad33"
    fill-opacity="0.5"
    d="M 30 30 L 70 30 L 70 70 L 30 70 Z"></path>
  <path
    stroke="red"
    transform="rotate(45 50 50)"
    d="M 30 30 L 70 30 L 70 70 L 30 70 Z"></path>
  <path
    stroke="green"
    transform="translate(-20 -20)"
    d="M 30 30 L 70 30 L 70 70 L 30 70 Z"></path>
  <path
    stroke="blue"
    transform="skewX(25)"
    d="M 30 30 L 70 30 L 70 70 L 30 70 Z"></path>
  <path
    stroke="yellow"
    transform="scale(1.5 1.5)"
    d="M 30 30 L 70 30 L 70 70 L 30 70 Z"></path>

Animating shapes

SVG contains a few elements that control animations such as <animate />, <animateMotion /> and <animateTransform />.

Adding an animate element as a child to a shape, allows you to animate properties of its parent. Using the attributeName specifies the name of the attribute to animate.

<svg>
  <circle cx="50" cy="50" r="5" fill="yellow">
    <animate
      attributeName="r"
      from="1"
      to="50"
      dur="12s"
      repeatCount="indefinite"></animate>
  </circle>
</svg>
01020304050607080901000102030405060708090100

Animating transforms

The animateTransform element can animate transformations on a target element.

<path
    stroke="#00487d"
    fill="var(--color-highlight)"
    stroke-width="2"
    fill-opacity="0.25"
    d="M30 60 L50 20 70 60 Z"
  >
    <animateTransform
      attributeName="transform"
      type="rotate"
      from="0 50 50"
      to="360 50 50"
      dur="8s"
      repeatCount="indefinite"></animateTransform>
  </path>
01020304050607080901000102030405060708090100

Keyframes and Tweens

When animation used to be done by hand, master animators drew certain key frames and delegated drawing the frames in between to juniors. We use these terms in animation to describe Keyframes where we declare the position of a frame and Tweens where we describe how we get to the next Keyframe.

CSS allows us to use Keyframes for animatable properties (which are essentially anything with a numerical or colour value) by declaring the animation property, or its sub-properties and a @keyframe at-rule.

This example uses CSS Keyframes to animate a ball:

01020304050607080901000102030405060708090100

here’s the css I used:

@keyframes bounce {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(80px);
  }
}
.ball {
  animation-name: bounce;
  animation-duration: 1s;
  animation-timing-function: ease-in-out;
  animation-delay: 0s;
  animation-iteration-count: infinite;
  animation-direction: alternate;
  animation-fill-mode: forwards;
  fill: var(--color-highlight);
}

Many animations can also be created using CSS by targeting SVG element properties. However CSS can’t target all SVG properties. The d property is a good example of a SVG element property that can’t be targeted with CSS.

Animating with a javascript library

Here, I’m using GSAP to scale the circle’s X and Y dimensions to simulate how balls deform when colliding with the floor:

01020304050607080901000102030405060708090100