Animating a Mini Scene in SVG

in Front-end

You might have noticed we recently built a new version of our homepage, and part of the redesign features “mini scenes,” which are comprised of CSS animations applied to an SVG. Our goal? To summarize the Code School experience while making the content as scannable as possible. The mini scenes we created for the new homepage use short headings and paragraphs, and tie everything together with animation — creating easy-to-consume content. So in this blog post, I’ll break down how we created one of those scenes, which changes from a static representation of our course UI into an animation representing the flow of completing a challenge.

Add Some Phhfft, Foomp, Boing, and Swooosh

There are some newfangled apps for prototyping animations out there (Framer, Pixate, etc.), but we haven’t gotten into them yet. Knowing we wanted some animations on the homepage, we started off with a sketch of the page and some vectors created in Illustrator. With these, we talked through how each illustration should animate. And if you’ve never had one of these conversations, they’re a lot of fun due to the hand motions and sound effects you use to describe what should happen. For this scene, Justin wanted to represent the challenge process: Write code, submit, succeed. We talked about having the code animate in, the mouse simulate a click, and the success overlay animate in.

Prepping the File

For this (and many animations), you need to able to target specific elements and groups of elements. For this example, we needed to make sure the z-index of elements were correct. Both of these can be handled in the source file, but I did a combination of both. To be sure the overlay was above the UI but not above the mouse, I grouped them together and named each group.

Naming elements or groups in the source file results in ugly IDs, and I prefer to use classes in the hopes that I can avoid repeating myself when possible. To do this, I use a combination of DevTools and the editor to find elements and add the necessary classes.

<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In -->
<svg version="1.1"
 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
 x="0px" y="0px" width="272px" height="230.6px" viewBox="0 0 272 230.6" enable-background="new 0 0 272 230.6"
 xml:space="preserve">
<defs>
</defs>
<g>
 <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="136" y1="192" x2="136" y2="32">
 <stop offset="0" style="stop-color:#B1BFC3"/>
 <stop offset="1" style="stop-color:#F1F2F3"/>
 </linearGradient>
 <path fill="url(#SVGID_1_)" d="M0,32v144c0,8.8,7.2,16,16,16h240c8.8,0,16-7.2,16-16V32H0z"/>
 <linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="0" y1="16" x2="272" y2="16">
 <stop offset="0" style="stop-color:#22ABC6"/>
 <stop offset="1" style="stop-color:#626EB3"/>
 </linearGradient>
 <path fill="url(#SVGID_2_)" d="M272,32V16c0-8.8-7.2-16-16-16H16C7.2,0,0,7.2,0,16v16H272z"/>
 <g>
 <path fill="#22ABC6" d="M64,20c0,1.1,0.9,2,2,2h180c1.1,0,2-0.9,2-2v-8c0-1.1-0.9-2-2-2H66c-1.1,0-2,0.9-2,2V20z"/>
 </g>
 <circle fill="#626EB3" cx="20" cy="16" r="4"/>
 <circle fill="#626EB3" cx="32" cy="16" r="4"/>
 <circle fill="#626EB3" cx="44" cy="16" r="4"/>
 <linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="52" y1="192" x2="52" y2="32">
 <stop offset="0" style="stop-color:#F15B51"/>
 <stop offset="1" style="stop-color:#F89E2B"/>
 </linearGradient>
 <path fill="url(#SVGID_3_)" d="M104,32H0v144c0,8.8,7.2,16,16,16h88V32z"/>
 <g>
 <linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="188" y1="152" x2="188" y2="48">
 <stop offset="0" style="stop-color:#5D5B6A"/>
 <stop offset="1" style="stop-color:#3C3D46"/>
 </linearGradient>
 <path fill="url(#SVGID_4_)" d="M256,148c0,2.2-1.8,4-4,4H124c-2.2,0-4-1.8-4-4V52c0-2.2,1.8-4,4-4h128c2.2,0,4,1.8,4,4V148z"/>
 </g>
 <path fill="#F15B51" d="M248,160h-56c-4.4,0-8,3.6-8,8s3.6,8,8,8h56c4.4,0,8-3.6,8-8S252.4,160,248,160z"/>
 <linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="133" y1="64" x2="171" y2="64">
 <stop offset="0" style="stop-color:#FFCF30"/>
 <stop offset="1" style="stop-color:#F89E2B"/>
 </linearGradient>

 <line class="do-codeOne--a" fill="none" stroke="url(#SVGID_5_)" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" x1="136" y1="64" x2="168" y2="64"/>
 <linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="149" y1="76" x2="219" y2="76">
 <stop offset="0" style="stop-color:#C0D84E"/>
 <stop offset="1" style="stop-color:#5CBC6A"/>
 </linearGradient>

 <line class="do-codeOne--b" fill="none" stroke="url(#SVGID_6_)" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" x1="152" y1="76" x2="216" y2="76"/>
 <linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="149" y1="88" x2="203" y2="88">
 <stop offset="0" style="stop-color:#F89E2B"/>
 <stop offset="1" style="stop-color:#F05060"/>
 </linearGradient>

 <line class="do-codeOne--c" fill="none" stroke="url(#SVGID_7_)" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" x1="152" y1="88" x2="200" y2="88"/>
 <linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="165" y1="100" x2="211" y2="100">
 <stop offset="0" style="stop-color:#22ABC6"/>
 <stop offset="1" style="stop-color:#626EB3"/>
 </linearGradient>

 <line class="do-codeTwo--a" fill="none" stroke="url(#SVGID_8_)" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" x1="168" y1="100" x2="208" y2="100"/>
 <linearGradient id="SVGID_9_" gradientUnits="userSpaceOnUse" x1="165" y1="112" x2="235" y2="112">
 <stop offset="0" style="stop-color:#FFCF30"/>
 <stop offset="1" style="stop-color:#F89E2B"/>
 </linearGradient>

 <line class="do-codeTwo--b" fill="none" stroke="url(#SVGID_9_)" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" x1="168" y1="112" x2="232" y2="112"/>
 <linearGradient id="SVGID_10_" gradientUnits="userSpaceOnUse" x1="165" y1="124" x2="187" y2="124">
 <stop offset="0" style="stop-color:#C0D84E"/>
 <stop offset="1" style="stop-color:#5CBC6A"/>
 </linearGradient>

 <line class="do-codeTwo--c" fill="none" stroke="url(#SVGID_10_)" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" x1="168" y1="124" x2="184" y2="124"/>
 <g>
 <path fill="#F89E2B" d="M88,116c0,2.2-1.8,4-4,4H20c-2.2,0-4-1.8-4-4V92c0-2.2,1.8-4,4-4h64c2.2,0,4,1.8,4,4V116z"/>
 </g>
 <g>
 <path class="do-taskTwo" fill="#F89E2B" d="M88,156c0,2.2-1.8,4-4,4H20c-2.2,0-4-1.8-4-4v-24c0-2.2,1.8-4,4-4h64c2.2,0,4,1.8,4,4V156z"/>
 </g>
 <line fill="none" stroke="#F15B51" stroke-width="6" stroke-miterlimit="10" x1="16" y1="48" x2="48" y2="48"/>
 <line fill="none" stroke="#F15B51" stroke-width="6" stroke-miterlimit="10" x1="16" y1="60" x2="88" y2="60"/>
 <line fill="none" stroke="#F15B51" stroke-width="6" stroke-miterlimit="10" x1="16" y1="72" x2="64" y2="72"/>
</g>
<g class="do-mouse" id="do-mouse_1_">
 <polygon opacity="0.3" fill="#3C3D46" points="231.3,211.8 218,220.7 218,164 266.5,202.4 248.6,205.4 257.6,222.2 240.7,230.6
 "/>
 <polygon fill="#FFFFFF" points="229.3,207.8 216,216.7 216,160 264.5,198.4 246.6,201.4 255.6,218.2 238.7,226.6 "/>
 <g>
 <g>
 <linearGradient id="SVGID_11_" gradientUnits="userSpaceOnUse" x1="246.6261" y1="218.4731" x2="218.2343" y2="169.2972">
 <stop offset="0" style="stop-color:#3C3D46"/>
 <stop offset="1" style="stop-color:#5D5B6A"/>
 </linearGradient>
 <polygon fill="url(#SVGID_11_)" points="240.5,198.4 254.9,196 220,168.3 220,209.2 230.8,202 240.5,221.3 250.1,216.5 "/>
 </g>
 <polygon fill="#3C3D46" points="230.8,202 240.5,221.3 245.4,218.8 220,168.3 220,209.2 "/>
 </g>
</g>
<g class="do-bg-success" id="do-bg-success_1_">
 <linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="136" y1="192" x2="136" y2="31.4375">
 <stop offset="0" style="stop-color:#5CBC6A"/>
 <stop offset="1" style="stop-color:#C0D84E"/>
 </linearGradient>
 <path opacity="0.95" fill="url(#SVGID_12_)" d="M0,31.4v146c0,8,7.7,14.6,17,14.6h238c9.3,0,17-6.6,17-14.6v-146H0z"/>
 <g class="do-check" id="do-check_1_">
 <ellipse fill="#5CBC6A" cx="137.8" cy="111.7" rx="36.4" ry="36.5"/>
 <ellipse fill="#F1F2F3" cx="132.4" cy="104.4" rx="36.4" ry="36.5"/>
 <linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="132.3846" y1="133.6136" x2="132.3846" y2="75.2273">
 <stop offset="0" style="stop-color:#C0D84E"/>
 <stop offset="1" style="stop-color:#5CBC6A"/>
 </linearGradient>
 <ellipse fill="url(#SVGID_13_)" cx="132.4" cy="104.4" rx="29.1" ry="29.2"/>
 <polyline class="do-checkmark" id="do-checkmark_1_" fill="none" stroke="#F1F2F3" stroke-width="4" stroke-miterlimit="10" points="117.8,104.4
 127.5,114.2 146.9,94.7 "/>
 </g>
</g>
</svg>

See the Pen Animating a Mini Scene in SVG (2) by Code School (@codeschool) on CodePen.

Minimum Viable Animation

When first working with an animation, I start with a Minimum Viable Animation, or MVA. My MVA is to make something fade in by setting its opacity to 0 and animating to an opacity of 1. Here, all of the elements that will animate in are comma separated so that they’re hidden when they start. What this does is allow the zero state of the scene to be visible and build up from there. The first animation I created was fadeIn, which will animate to opacity: 1. This is what I use to start with each element until I make it better.

// Only used to center the demo
svg
 display: block
 margin: 2em auto 0

// Hide all "staged" elements
.do-checkmark,
.do-check,
.do-bg-success,
.do-mouse,
.do-taskTwo,
.do-codeTwo--a,
.do-codeTwo--b,
.do-codeTwo--c,
.do-codeOne--a,
.do-codeOne--b,
.do-codeOne--c
 opacity: 0

// ----- Keyframes ----- //

// Fade In - invisible to visible
@keyframes fadeIn
 to
 opacity: 1

See the Pen Animating a Mini Scene in SVG (3) by Code School (@codeschool) on CodePen.

Scene One

To simulate the challenge experience, the beginning of the animation should require code typed in, followed by a new task box. Here, I used the MVA with staggered delays based on the length of each animation.

See the Pen Animating a Mini Scene in SVG (4) by Code School (@codeschool) on CodePen.

The pacing felt good and it’s the order that the elements should arrive in. This MVA allows for the proof of concept, which is then built upon. The task box is fine fading in, but a line of code wouldn’t really fade in (unless someone was copying and pasting). The code lines should grow from left to right. To achieve this, I added a new animation: growX. This transforms scaleX to 1. To set the initial state, I moved the classes into the scene area of the CSS and applied a scaleX of 0. With the new animation set up, I changed the animation for each of the code lines.

See the Pen Animating a Mini Scene in SVG (5) by Code School (@codeschool) on CodePen.

The scale transform requires a transform-origin, and this is a bit tricky for items placed at specific coordinates in the view box. I haven’t found a way to automate this, so I used xScope to find the edges of each element.

See the Pen Animating a Mini Scene in SVG (6) by Code School (@codeschool) on CodePen.

If you’d like to keep it in the browser, here’s a snippet you can use to output the position of an element — as you can see, I was close but not exact with mine.

See the Pen Animating a Mini Scene in SVG (7) by Code School (@codeschool) on CodePen.

To complete scene one, all of the transform-origins are set and each element gets the growX animation, except for the task box.

Scene Two

Scene two is very similar, but with different values for the transform-origins and the timing staggered.

See the Pen Animating a Mini Scene in SVG (8) by Code School (@codeschool) on CodePen.

Scene Three

Scene three simulates a submission of the code, so a mouse comes in for a click and then, since the code is correct, a success overlay comes in.

Now, the mouse requires a new animation, as we want it to grow into size and then simulate a click.

The success background fades in, and then the check area uses a new animation that was initially described with some sounds and hand gestures. Rather than recreate the wheel, I used bounceInUp from animate.css.

See the Pen Animating a Mini Scene in SVG (9) by Code School (@codeschool) on CodePen.

Once we saw that in action, we wanted the actual checkmark to appear separately. Here you can see that it now has its own class, along with a separate animation.

See the Pen Animating a Mini Scene in SVG (10) by Code School (@codeschool) on CodePen.

Adding a New Element to the SVG

Next up was adding a “click marker,” which sent me back to the artboard. I needed to add another element — a circle that would appear below the mouse and above the button. No big whoop, right?

This is where the big flaw in editing the code rather than the source file comes in. Not a major deal, but I now had fresh code from Illustrator without any of my classes.

See the Pen Animating a Mini Scene in SVG (11) by Code School (@codeschool) on CodePen.

To get the animation back up and running, I needed to add the classes back in. The click point required a new animation because it needed to come into partial opacity and right back out.

See the Pen Animating a Mini Scene in SVG (2) by Code School (@codeschool) on CodePen.

Finished? Not Quite

If the animation is supposed to run immediately, this would be all that was necessary. However, we wanted them to run when they were in view.

The first step is scoping the animations to a class of .is-active. The “up” state of each element is set and the animation CSS is scoped to the active class. Next, we need to add the class. For this we used a scrollToggle in JavaScript to trigger it when the window is scrolled to within 40 pixels of the top of the parent element.

See the Pen Animating a Mini Scene in SVG (13) by Code School (@codeschool) on CodePen.

OMGimize

The code that comes out of Illustrator has some unnecessary bloat, so I used SVGOMG to optimize the code. The tricky bit is that since many layers are named the same in the multiple SVGs I was creating, I had to manually scope the IDs and URLs within the code to prefix with do as well.

<div class="js-scrollTrigger">
<svg xmlns="http://www.w3.org/2000/svg" width="272" height="230.6" viewBox="0 0 272 230.6"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="136" y1="192" x2="136" y2="32"><stop offset="0" stop-color="#B1BFC3"/><stop offset="1" stop-color="#F1F2F3"/></linearGradient><path fill="url(#a)" d="M0 32v144c0 8.8 7.2 16 16 16h240c8.8 0 16-7.2 16-16V32H0z"/><linearGradient id="b" gradientUnits="userSpaceOnUse" y1="16" x2="272" y2="16"><stop offset="0" stop-color="#22ABC6"/><stop offset="1" stop-color="#626EB3"/></linearGradient><path fill="url(#b)" d="M272 32V16c0-8.8-7.2-16-16-16H16C7.2 0 0 7.2 0 16v16h272z"/><path fill="#22ABC6" d="M64 20c0 1.1.9 2 2 2h180c1.1 0 2-.9 2-2v-8c0-1.1-.9-2-2-2H66c-1.1 0-2 .9-2 2v8z"/><circle fill="#626EB3" cx="20" cy="16" r="4"/><circle fill="#626EB3" cx="32" cy="16" r="4"/><circle fill="#626EB3" cx="44" cy="16" r="4"/><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="52" y1="192" x2="52" y2="32"><stop offset="0" stop-color="#F15B51"/><stop offset="1" stop-color="#F89E2B"/></linearGradient><path fill="url(#c)" d="M104 32H0v144c0 8.8 7.2 16 16 16h88V32z"/><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="188" y1="152" x2="188" y2="48"><stop offset="0" stop-color="#5D5B6A"/><stop offset="1" stop-color="#3C3D46"/></linearGradient><path fill="url(#d)" d="M256 148c0 2.2-1.8 4-4 4H124c-2.2 0-4-1.8-4-4V52c0-2.2 1.8-4 4-4h128c2.2 0 4 1.8 4 4v96z"/><path fill="#F15B51" d="M248 160h-56c-4.4 0-8 3.6-8 8s3.6 8 8 8h56c4.4 0 8-3.6 8-8s-3.6-8-8-8z"/><linearGradient id="e" gradientUnits="userSpaceOnUse" x1="133" y1="64" x2="171" y2="64"><stop offset="0" stop-color="#FFCF30"/><stop offset="1" stop-color="#F89E2B"/></linearGradient><path class="do-codeOne--a" fill="none" stroke="url(#e)" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" d="M136 64h32"/><linearGradient id="f" gradientUnits="userSpaceOnUse" x1="149" y1="76" x2="219" y2="76"><stop offset="0" stop-color="#C0D84E"/><stop offset="1" stop-color="#5CBC6A"/></linearGradient><path class="do-codeOne--b" fill="none" stroke="url(#f)" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" d="M152 76h64"/><linearGradient id="g" gradientUnits="userSpaceOnUse" x1="149" y1="88" x2="203" y2="88"><stop offset="0" stop-color="#F89E2B"/><stop offset="1" stop-color="#F05060"/></linearGradient><path class="do-codeOne--c" fill="none" stroke="url(#g)" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" d="M152 88h48"/><linearGradient id="h" gradientUnits="userSpaceOnUse" x1="165" y1="100" x2="211" y2="100"><stop offset="0" stop-color="#22ABC6"/><stop offset="1" stop-color="#626EB3"/></linearGradient><path class="do-codeTwo--a" fill="none" stroke="url(#h)" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" d="M168 100h40"/><linearGradient id="i" gradientUnits="userSpaceOnUse" x1="165" y1="112" x2="235" y2="112"><stop offset="0" stop-color="#FFCF30"/><stop offset="1" stop-color="#F89E2B"/></linearGradient><path class="do-codeTwo--b" fill="none" stroke="url(#i)" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" d="M168 112h64"/><linearGradient id="j" gradientUnits="userSpaceOnUse" x1="165" y1="124" x2="187" y2="124"><stop offset="0" stop-color="#C0D84E"/><stop offset="1" stop-color="#5CBC6A"/></linearGradient><path class="do-codeTwo--c" fill="none" stroke="url(#j)" stroke-width="6" stroke-linecap="round" stroke-miterlimit="10" d="M168 124h16"/><path fill="#F89E2B" d="M88 116c0 2.2-1.8 4-4 4H20c-2.2 0-4-1.8-4-4V92c0-2.2 1.8-4 4-4h64c2.2 0 4 1.8 4 4v24z"/><path class="do-taskTwo" fill="#F89E2B" d="M88 156c0 2.2-1.8 4-4 4H20c-2.2 0-4-1.8-4-4v-24c0-2.2 1.8-4 4-4h64c2.2 0 4 1.8 4 4v24z"/><path fill="none" stroke="#F15B51" stroke-width="6" stroke-miterlimit="10" d="M16 48h32M16 60h72M16 72h48"/><g class="do-bg-success"><linearGradient id="k" gradientUnits="userSpaceOnUse" x1="136" y1="192" x2="136" y2="31.438"><stop offset="0" stop-color="#5CBC6A"/><stop offset="1" stop-color="#C0D84E"/></linearGradient><path opacity=".95" fill="url(#k)" d="M0 31.4v146c0 8 7.7 14.6 17 14.6h238c9.3 0 17-6.6 17-14.6v-146H0z"/><g class="do-check"><ellipse fill="#5CBC6A" cx="137.8" cy="111.7" rx="36.4" ry="36.5"/><ellipse fill="#F1F2F3" cx="132.4" cy="104.4" rx="36.4" ry="36.5"/><linearGradient id="l" gradientUnits="userSpaceOnUse" x1="132.385" y1="133.614" x2="132.385" y2="75.227"><stop offset="0" stop-color="#C0D84E"/><stop offset="1" stop-color="#5CBC6A"/></linearGradient><ellipse fill="url(#l)" cx="132.4" cy="104.4" rx="29.1" ry="29.2"/><path class="do-checkmark" fill="none" stroke="#F1F2F3" stroke-width="4" stroke-miterlimit="10" d="M117.8 104.4l9.7 9.8 19.4-19.5"/></g></g><circle class="do-clickPoint" opacity=".5" fill="#FFF" cx="218.5" cy="165.5" r="16.8"/><g class="do-mouse"><path opacity=".3" fill="#3C3D46" d="M231.3 211.8l-13.3 8.9V164l48.5 38.4-17.9 3 9 16.8-16.9 8.4"/><path fill="#FFF" d="M229.3 207.8l-13.3 8.9V160l48.5 38.4-17.9 3 9 16.8-16.9 8.4"/><linearGradient id="m" gradientUnits="userSpaceOnUse" x1="246.626" y1="218.473" x2="218.234" y2="169.297"><stop offset="0" stop-color="#3C3D46"/><stop offset="1" stop-color="#5D5B6A"/></linearGradient><path fill="url(#m)" d="M240.5 198.4l14.4-2.4-34.9-27.7v40.9l10.8-7.2 9.7 19.3 9.6-4.8"/><path fill="#3C3D46" d="M230.8 202l9.7 19.3 4.9-2.5-25.4-50.5v40.9"/></g></svg>
</div>

See the Pen Animating a Mini Scene in SVG (14) by Code School (@codeschool) on CodePen.

Boom

And there you have it — one animation down! Creating these for our new homepage was a great exercise for me, and I hope this can help you add some life to your own projects if you decide to. If you’d like to learn SVG, you can take our You, Me & SVG course. And don’t forget to go check out all of the new animations (including the one I talked about here) on our new codeschool.com homepage.

Code School

Code School teaches web technologies in the comfort of your browser with video lessons, coding challenges, and screencasts. We strive to help you learn by doing.

Visit codeschool.com

About the Author

Dan Denney

I’m a seriously good copy and paster. Front-end dev for @codeschool, lover of all things web design/dev related and founder of @frontendconf.

Might We Suggest