This tutorial will outline creating animated SVG instruments that can be played by clicking, tapping or using your keyboard! We’ll focus on creating a electric guitar and bass in this tutorial but the techniques we’ll use could be applied to any kind of animated musical instruments. We’ll be using GreenSock’s TweenMax animation library, Morph plugin and the <audio> element.

Drawing the SVG

I use Adobe Illustrator for this typically but there are many alternatives to choose from, such as Affinity Designer, Sketch or Inkscape. I’ve kept the style flat and simple in my examples, as I find this makes the drawing and animation much simpler. Before moving on to animating our SVGs there are a couple of things to keep in mind:

Hot Tip
I often find it helpful to dot a few photos of what I’m trying to draw around my art board for reference. In the case of a guitar (which is a bit of a tricky shape) it might even be easier to trace the outline from a photo.

<g> Can Save Your Life…

Maybe that’s a little dramatic, but keeping your groups organised and well named will make animating the SVG much easier later on!

“The g element is a container used to group other SVG elements. Transformations applied to the g element are performed on all of its child elements, and any of its attributes are inherited by its child elements.”MDN

We’ll take advantage of this behavior when animating our SVG, by applying animations to these groups rather than each individual component of the snare drum, we can achieve complex animations with relatively simple code.

Hot Tip
If your SVG drawing app has an option to do so, enabling XML id names allows you to edit/see the actual id attribute of the elements and groups that we’ll use to target them. (In Illustrator: Preferences >; Units >; Identify Objects By: >; XML ID)

Draw Everything, Even If We Don’t Want To See It

This may seem a little counter-intuitive at first, but there may be parts we need to draw that are not be visible but will be helpful when animating. For example, I’ve drawn some musical notes that will ‘spring’ out of the guitar when played to liven up the animation. I don’t want these visible when by default but I’ll keep them visible when exporting the SVG, give them all a class and set their display property to none via that class selector in our stylesheet.

SVG Bass Guitar Groups

Mighty Morphin’ SVGs

Greensock’s Morph plugin works by tweening a path to another path. This will be perfect for making our guitar/bass strings “wobble”, but for this to work we need another set of paths for our strings to morph to. We can do this by making our strings into polylines with a slight ‘kink’ in them. We’ll group these paths together and give ensure their strokes have no thickness or color. This will mean they won’t be visible to the user but will be present for us to use in our morph animations.


Exporting the SVG

Export your file with all your groups visible as a .svg and open it up in a text editor, we could put this straight into our HTML document. However, if you’re using this in a production environment I recommend optimising the SVG first. You could do this manually by removing the whitespace etc but online tools such as SVGOMG can do this automatically. Alternatively Sara Soueidan has a great list of SVG optimisation tools.

Get Some Riffs

We’ve prepared our SVGs, now we need some tunes for them to play! I’ve used the main riff from Deep Purples classic Smoke on the Water. We’ll need separate audio files for each different note we want our instruments to play. I exported the audio used in the demo from Apple’s Logic (I recorded the guitar and bass is from a midi track). However, there are many other resources for getting free audio samples on the web and even free digital audio workstations out there.

We can include our audio using the <audio> element. Then we set the src attribute on either the <audio> element or a child <source> element where we also need to include its type (the audio file format).
We give the audio elements IDs so that we can target them.

 <audio id="bass-E1">   <source src="mp3/bass-E1.mp3" type="audio/mp3"> </audio> 

(Pre)Load The Audio

Adding the attribute preload="auto" to our elements we can tell the browser the user needs this, to ensure it’s gets downloaded as soon as possible. That’s how it works in most desktop browsers anyway.

 <audio id="bass-E1" preload="auto">   <source src="mp3/bass-E1.mp3" type="audio/mp3"> </audio> 

Rock Out!

Now we have our SVGs and audio ready, we need to set up our animations and audio, then put it all together…

Let’s start by storing the DOM elements we need as variables. There’s two main things we need to target:

  1. The elements of the SVG we need to animate or attach event handlers to.
  2. The <audio> elements.
 bassGuitar = document.getElementById('Bass_Guitar_1'); bassAudioE1 = document.getElementById('Bass-E1'); 

We’ll do this for all the SVG elements we will need to animate and all the audio required.

Let’s Make Some Noise

We’ll start by simply triggering the audio we want play with the .play() method.; 

Simple as that! Well nearly… This works great, but if we call the method again while the audio is already playing, it won’t work until the audio has stopped. We want the sound to be triggered from the beginning of the audio file every time the user triggers it. So we need to set the audio start position back to the beginning is every time it’s being triggered.

 bassAudioE1.currentTime = 0;; 

And we’ll wrap that in a function so we can easily call it later on.

 function playBassAudio () {   bassAudioE1.currentTime = 0;; } 

This will trigger a single note each time we call it. One note doesn’t make for a particularly interesting tune though! Using a counter variable and some conditional statements we can trigger different audio at different times. I came up with a set of conditional statements based on the bass line I wanted to play:

 var i = 0; function playBassAudio() {     ++i;     if (i === 1) {         bassAudioE1.currentTime = 0;;     } else if (i === 2) {         bassAudioF1.currentTime = 0;;     } else if (i === 3) {         bassAudioFS1.currentTime = 0;;     } else if (i >; 3 && i < 21) { bassAudioG1.currentTime = 0;; } else if (i === 21 || i === 22 || i === 23) { bassAudioC2.currentTime = 0;; } else if (i === 24) { bassAudioAS1.currentTime = 0;; } else if (i >; 24 && i < 29) {         bassAudioG1.currentTime = 0;;     } else if (i === 29 ) {         bassAudioA1.currentTime = 0;;         i = 0;     } } 

This can look pretty crazy, but is actually quite simple when broken down. It’s just a set of conditional statements to trigger different audio based on the value of the index variable i, this increments each time the user ‘plays’ the instrument. This will completely vary based on the audio you use and what you want the resulting tune to be. In fact, I used a very different strategy for the guitar audio:

 var i = 0; function playGuitarAudio() {     if (i < 12) {         ++i;     } else {         i = 1;     }     var guitarAudio = document.getElementById('audio-' + i);; } 

This works by assigning a different element to the guitarAudio variable based on the value of i when the function is called. This works well for the guitar because the audio is slightly different for each of the notes played, but probably wouldn’t work as well for the bass as there would need to be a lot of audio files, most of which would be the same. Depending on the audio samples you are using and the desired output to be, you may have to find a different solution.

We can then call our audio function using an event handler for any click on our instrument.

 function rockOutBass() {     playBassAudio(); } bassGuitar.addEventListener('click', rockOut); 

Now you should be able to play away!

Give It Some Vibe

Our instruments are making some noise now, but they’re not very exciting yet, let’s breathe a little life into them with some animation…
We’ll start with a really simple and subtle css animation to give the impression our instruments are ‘floating’, we’ll do this by applying a transform: translate3d to the move the instruments vertically and then back to their original position. We can accentuate this movement by scaling the instruments’ shadow at the same time using transform: scale.

 #Bass_Shadow_1 {     animation: shadow 4s infinite linear;     transform-origin: 50% 50%; }  #Bass_Guitar_1 {     animation: hover 4s infinite linear; }  @keyframes hover {     from, to {         transition-timing-function: ease-in-out;         transform: translate3d(0,0,0);     }     50% {         transition-timing-function: ease-in-out;         transform: translate3d(0,1.5%,0);     } }  @keyframes shadow {     from, to {         transition-timing-function: ease-in-out;         transform: scale(0.95,1);     }     50% {         transition-timing-function: ease-in-out;         transform: scale(1.05,1);     } } 

Our instruments look great while they’re not being played, now we want them to look great when they’re being played…

Time To Tween

Enter Greensock’s TweenMax and Morph plugin. TweenMax is a JavaScript library that handles tweening one or more properties of any object (or array of objects) over time. I throughly recommend checking out Greensock’s TweenMax docs and examples to get to grips with the basics but each Tween animation but each can be broken down into a few simple steps:

  1. Create a new timeline
  2. Tween a property (or group of properties) of the element
  3. (In the case of our instruments) Tween back to the original state

Using the Morph plugin works in the same way, however the properties (the point and path data) are handled automatically by the plugin. We’ll use this in conjunction with easing (this varies the rate at which the tween occurs.) to make our guitar/bass strings wobble. We’ll do this by morphing our visible strings to the invisible ‘kinked’ strings we drew earlier.

 //Declare timeline var pluck = new TimelineMax({     paused: true }) //Define what happens when that timeline is played, 0.1, {ease: Expo.easeOut,morphSVG: {points: stringPlucked_1.getAttribute('points')}})      .to(stringStraight_1, 1, {morphSVG: {points: stringStraight_1.getAttribute('points')},ease: Elastic.easeOut.config(2, 0.04)}) 

There’s a lot going on here so let’s break it down. First we declare a new timeline, that’s paused by default (we don’t want it to play on load, we want to trigger it). Then declaring the paths (or polylines in this case) we want to morph to/from. Defining the ease properties and then morphing back to the original position (otherwise the animation would end with the strings in the ‘plucked’ position).

Hot Tip
Easing is one of the most useful tools GreenSock provides and fundamental to animation in general to give your animation the right ‘feel’. GreenSock have a great ease visualizer which can help you decide what’s right for your animation.

Now repeat this for all the strings:

 .to(stringStraight_2, 0.1, {ease: Expo.easeOut,morphSVG: {points: stringPlucked_2.getAttribute('points')}}, '-=1.1') .to(stringStraight_2, 1, {morphSVG: {points: stringStraight_2.getAttribute('points')},ease: Elastic.easeOut.config(2,0.04)}, '-=1')  .to(stringStraight_3, 0.1, {ease: Expo.easeOut,morphSVG: {points: stringPlucked_3.getAttribute('points')}}, '-=1.1') .to(stringStraight_3, 1, {morphSVG: {points: stringStraight_3.getAttribute('points')},ease: Elastic.easeOut.config(2, 0.04)}, '-=1')  .to(stringStraight_4, 0.1, {ease: Expo.easeOut,morphSVG: {points: stringPlucked_4.getAttribute('points')}}, '-=1.1') .to(stringStraight_4, 1, {morphSVG: {points: stringStraight_4.getAttribute('points')},ease: Elastic.easeOut.config(2, 0.04)}, '-=1') 

Note the '-=1.1' and "-=1" arguments for the extra strings, this is to ensure they all start at the same time.

When the animation is played our strings will appear to be ‘plucked’ and then wobble back to their original position!
We can apply a similar effect to the amplifier by scaling the speaker grill up and back to its original size:

 .to(speaker_1, 0.1, {scale: 1.015, transformOrigin: '50% 50%', ease: Expo.easeOut}, '-=1.1') .to(speaker_1, 0.3, {scale: 1, transformOrigin: '50% 50%', ease: Elastic.easeOut}, '-=1') 

And then reveal the musical notes we made invisible earlier. (You could skip this if you’d like to keep your instruments more ‘realistic’).

 .to(note_1, 0.4, {x:'-50%', y:'-50%'}, '-=1.1') .to(note_1, 0.2, {opacity:1 , ease:SlowMo.easeIn}, '-=1.1') .to(note_1, 0.2, {opacity:0 , ease:SlowMo.easeOut}, '-=0.9')  .to(note_2, 0.4, {x:'50%', y:'-50%'}, '-=1.1') .to(note_2, 0.2, {opacity:1 , ease:SlowMo.easeIn}, '-=1.1') .to(note_2, 0.2, {opacity:0 , ease:SlowMo.easeOut}, '-=0.9')  .to(note_3, 0.4, {x:'-20%', y:'-50%'}, '-=1.1') .to(note_3, 0.2, {opacity:1 , ease:SlowMo.easeIn}, '-=1.1') .to(note_3, 0.2, {opacity:0 , ease:SlowMo.easeOut}, '-=0.9')  .to(note_4, 0.4, {x:'50%', y:'-25%'}, '-=1.1') .to(note_4, 0.2, {opacity:1 , ease:SlowMo.easeIn}, '-=1.1') .to(note_4, 0.2, {opacity:0 , ease:SlowMo.easeOut}, '-=0.9') 

This will move the notes by the defined values while fading them in out. I’ve applied the same technique to some sparks coming from the speaker.

 .to(spark_1, 0.4, {x:'-50%', y:'50%'}, '-=1.1') .to(spark_1, 0.2, {opacity:1 , ease:SlowMo.easeIn}, '-=1.1') .to(spark_1, 0.2, {opacity:0 , ease:SlowMo.easeOut}, '-=0.9')  .to(spark_2, 0.4, {x:'-50%', y:'-50%'}, '-=1.1') .to(spark_2, 0.2, {opacity:1 , ease:SlowMo.easeIn}, '-=1.1') .to(spark_2, 0.2, {opacity:0 , ease:SlowMo.easeOut}, '-=0.9')  .to(spark_3, 0.4, {x:'50%', y:'-50%'}, '-=1.1') .to(spark_3, 0.2, {opacity:1 , ease:SlowMo.easeIn}, '-=1.1') .to(spark_3, 0.2, {opacity:0 , ease:SlowMo.easeOut}, '-=0.9') 

Now we have all our animations defined, we can play and restart them (so the animation plays from the begging each time it’s triggered) in the function we defined earlier for our event listeners.

 function rockOutBass() {   playBassAudio();   pluck.restart();; } 

That’s it! Play away on your new animated SVG instruments! You can apply these same animation and audio techniques to guitar like I have, or any instrument!

See the Pen Design Bombs – Interactive Animated SVG Instruments Tutorial by Josh (@iamjoshellis) on CodePen.


To show main source of content: