July 22, 2019
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.
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
.
<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.
.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
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)}
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]);
}
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
.
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);
}
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:
If you got interested in building a similar animation yourself, here are a few more resources: