Dynamic SVG Animation with CSS Variables

Published

July 22, 2019

Dynamic SVG Animation with CSS Variables

Sometimes we want to create dynamic animations, that react to user input, like the mouse moving or selecting some checkboxes. Updating SVG elements according to this input has gotten a lot easier now that CSS Variables are usable in the browser. But to be able to change the SVGs styling via CSS Variables, there are a few things that we need to consider beforehand.

Export SVG

When the vector graphic is created in a graphical tool like Illustrator or Inkscape, it’s important to name the different layers, which should be animated later on. The steps for Illustrator are demonstrated in the image below. It’s essential to not just save the File as SVG, but to use the Export As Options. Finally, define the styling as Internal CSS and convert the fonts to Outlines. The Object IDs should be the Layer Names.

Exported SVG

<svg id="illustration" xmlns="http://www.w3.org/2000/svg" 
  viewBox="0 0 889.95 828.3">
  <defs>
    <style>
      .cls-1 { fill: #f2f2f2; }
      .cls-2 { fill: none; }
      ...
    </style>
  </defs>

  <title>Woman and Plants</title>

  <g id="flower">...</g>
  <g id="plant">...</g>
  <g id="leaf-center">...</g>
  	 ... 
</svg>

Once the SVG was exported, we’ll end up with code similar to the code snippet above. If there is lots of additional markup from the graphical tool used, I recommend to optimize it with SVGOMG. The next step is to extract the CSS within the <style> tag into a more global CSS stylesheet and then inline the SVG into the HTML, so it’s possible to access all its elements. Depending on how the SVG was exported, the layer names defined in the graphical tool are accessible via the id attribute of the different <g> elements like <g id="flower">...</g>. This is important for animating the different elements later on.

Defining the CSS Variables

.illustration {
	--c-accent: #ec3667;
	--c-dark: #3f3d56;
	--c-light: #cbcdda;
	--c-skin: #ffb9b9;
}

To adapt the different colors depending on what user input is given, we create CSS Variables, that can be changed at any time. The example below demonstrates what is meant by dynamically changing the variables on user input. Whenever a word is said or an input clicked, the whole animation and its colors update.

SVG Animation with Speech Recognition

Coloring the illustration with the CSS Variables

To link the variables and the illustration together, we have to find the internal CSS Snippet, which was moved to our global CSS file:

.cls-1 { fill: #f2f2f2; }
.cls-2 { fill: none; }
.cls-3{ fill: #5588a3 }
.cls-4{ fill: #ffb9b9 }

Replace all the static colors with the newly defined CSS Variables:

.cls-2{fill:none;stroke:var(--c-dark);stroke-miterlimit:10;stroke-width:2px}
.cls-3{fill:var(--c-dark)}
.cls-4{fill:var(--c-accent)}
.cls-8{fill:var(--c-skin)}

Making it dynamic

The last step is to update the variables whenever some user input is given (via mouse, speech, audio, … ). First, a set of different colors is defined, in my example I created an object with different color sets for each color like blue or orange.

const colors = {
  blue: ["#5588a3", "#00334e", "#e8e8e8", "#A0616A"],
  orange: ["#f7931e", "#f05a28", "#fff0bc", "#FDB797"],
  pink: ["#ec3667", "#3f3d56", "#cbcdda", "#ffb9b9"],
};

If the blue button was clicked I would update the CSS variables --c-dark, --c-accent, --c-skin and --c-light according to the array given in my colors object like seen in the code snippet below. This snippet also shows the way to update a CSS Variable. We’re calling the style.setProperty(<variable name>, <value>) on a DOM Element to update the variable.

const illustration = document.querySelector(".illustration")

function update(response) {
  illustration.style.setProperty("--c-accent", colors[response][0]);
  illustration.style.setProperty("--c-dark", colors[response][1]);
  illustration.style.setProperty("--c-light", colors[response][2]);
  illustration.style.setProperty("--c-skin", colors[response][3]);
}

Animating SVG

A lot of animation can be done natively in the browser. If we’re just animating regular DOM nodes like a div, it will be quite simple to do with CSS or the Web Animations API, but animating elements within a SVG is more complicated. SVG was primarily built to display vector graphics, but not to move and animate the different objects around. That’s why there are still a few browser inconsistencies across different browsers. Especially concerning transform-origin and animating transform of the geometric elements. Note that if you’re animating only a SVG element and not its children, that’s fine since that also behaves like a regular DOM node. So what can we do to get consistent SVG animations across all browsers?

There are two considerations: Are you building a complex animation, where a lot of elements are moving around or are you just fading some elements in and out and changing their colors? For most simple animations that just change opacity, stroke or fill, the animations can be created with pure CSS or the WAAPI, since these attributes are quite consistent across browsers. But whenever you’re moving a lot of elements, the best option would be to choose an animation library. Have a look at this handy Web animation toolkit. There are a few animation libraries that are optimized for animating SVG like GSAP or Snap.svg.

Animating SVG with GSAP

SVG Animation with Speech Recognition

In the Codepen example I chose GSAP to animate my graphic. First, a new timeline called tl is created. For the geometric elements in the back, we'd get all the IDs for the geometric elements and put them in an array geometry in the right order, so #rectangle would get animated before #circle-bottom. Next, the timeline calls the tl.staggerFromTo(geometry, ...) function to animate all those elements.

const tl = new TimelineLite();

const geometry = ['#geometry #rectangle', '#geometry #circle-bottom', '#geometry #circle-center', '#geometry #circle-left', '#geometry #circle-right','#geometry #circle-top', '#geometry #circle-top-2'];

tl.staggerFromTo(geometry, 1.4, { opacity: 0, scale: 0, transformOrigin: '50% 50%' }, { scale: 1, opacity: 1, ease: Elastic.easeOut.config(0.9, 0.4)}, .1');

For elements that should be animated on their own we can use the fromTo function, which allows you to define the state the animation should animate from and the state it should animate to. Like the woman sliding in from the right.

tl.fromTo('#woman', .4, {x : 40, opacity: 0}, {x: 0, opacity: 1, ease: Back.easeOut }, '-=.4');

Finally, whenever new user input is given, the timeline is played from the beginning:

function update(response) {
	...
	tl.play(0);
}

Summary

To round up let's have a look at the steps we need to go through to create an SVG animation that updates according to user input:

  1. Create a vector graphic in a graphics tool like illustrator
  2. Export and optimize it correctly for the browser
  3. Define CSS Variables and update the SVGs color fills with those variables
  4. Create an animation timeline (natively or with a SVG animation library)
  5. Update the variables whenever a user input happens (e.g. a button click)
  6. Replay the animation whenever a user input happens (e.g. a button click)

Resources

If you got interested in building a similar animation yourself, here are a few more resources:

More articles