SVG Animation with MorphSVG

Image for SVG Animation with MorphSVG

I was recently introduced to the MorphSVGPlugin from GreenSock and was very impressed. MorphSVG makes animating one path in an SVG into another one quick and easy. I wanted to get familiar with this new library, so I figured I would just jump right in. And since it is almost Halloween, what better way than to turn our Intuitive Company logo into a member of the undead.

Here is the full version:
(Click the logo to see it in action)

Let’s break this down step by step and see how this animation is actually happening.

Dependencies

First things first, you are going to need to dependencies:

TweenMax:
//cdnjs.cloudflare.com/ajax/libs/gsap/1.18.0/TweenMax.min.js

morphSVG:
https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/MorphSVGPlugin.min.js

*Note: MorphSVGPlugin is a bonus plugin for all Club GreenSock members. The URL above will only work on the CodePen domain. For more on this, visit http://greensock.com/morphSVG

Creating the Assets

Now that you have all your dependencies, the next thing we want to do is create our assets. I started with two SVGs that are exactly the same dimensions.

IC-Logo-blueic_vampire

It is important that the assets are the same size so that when we combine the paths into the same SVG they will align correctly.

Combining the SVGs

 

Once we have both SVGs, we need to combine all the paths together into one. This is as simple as copying all of the contents from one SVG into the other.

IC Logo:

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
       viewBox="0 0 67.6 77.3" enable-background="new 0 0 67.6 77.3" xml:space="preserve">
    <g>
      <path fill="#759ed0" d="M57.6,0H10C4.5,0,0,4.5,0,10v47.6c0,5.5,4.5,10,10,10h38.2v0l9.7,0l9.7,9.7V10C67.6,4.5,63.1,0,57.6,0z"/>
      <rect x="16.2" y="19.6" fill="#FFFFFF" width="3" height="28.5"/>
      <path fill="#FFFFFF" d="M40.9,45.7c4.1,0,7.8-2.1,9.9-5.3l2.6,1.7c-2.7,4-7.3,6.7-12.5,6.7c-8.3,0-15-6.7-15-15c0-8.3,6.7-15,15-15
        c5.2,0,9.8,2.7,12.5,6.7l-2.6,1.7c-2.1-3.2-5.8-5.3-9.9-5.3c-6.6,0-11.9,5.3-11.9,11.9S34.4,45.7,40.9,45.7z"/>
    </g>
</svg>

Vampire:

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
	 viewBox="0 0 67.6 77.3" enable-background="new 0 0 67.6 77.3" xml:space="preserve">
<g>
	<g>
		<g>
			<path fill="#D22D78" d="M56.9,0.9H10.6c-5.3,0-9.7,4.4-9.7,9.5v45.7c0,5.3,4.4,9.6,9.7,9.6h37.1v0h9.5l9.5,9.4v-9.4v-9.6v-9V10.4
				C66.7,5.3,62.3,0.9,56.9,0.9z"/>
			<path fill="#D22D78" d="M67.6,77.3L56.9,66.7l-46.2,0C4.8,66.7,0,61.9,0,56.2V10.4C0,4.8,4.9,0,10.6,0h46.3
				c5.8,0,10.7,4.8,10.7,10.4V77.3z M10.6,1.8c-4.8,0-8.8,3.9-8.8,8.6v45.7c0,4.8,3.9,8.7,8.8,8.7l47,0l8.2,8.1V10.4
				c0-4.7-4.1-8.6-8.9-8.6H10.6z"/>
		</g>
	</g>
	<ellipse fill="#4C001F" cx="12.8" cy="32.7" rx="3.2" ry="3.2"/>
	<ellipse fill="#4C001F" cx="54.6" cy="32.7" rx="3.2" ry="3.2"/>
	<g>
		<polygon fill="#4C001F" points="58,46.6 56,49 11.7,49 9.7,46.6"/>
		<polygon fill="#4C001F" points="22.7,46.7 16.1,54.1 9.7,46.6"/>
		<polygon fill="#4C001F" points="56.2,48.8 51.8,54.1 47.3,48.8"/>
	</g>
	<polygon fill="#FFFFFF" points="54.8,15 58.2,18.4 47.9,28.6 44.4,25.2"/>
	<polygon fill="#FFFFFF" points="10,18.4 13.5,15 23.9,25.2 20.4,28.6"/>
</g>
</svg>

Merged:

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
   viewBox="0 0 67.6 77.3" enable-background="new 0 0 67.6 77.3" xml:space="preserve">
<g>
  <!-- IC LOGO  -->
  <path fill="#759ed0" d="M57.6,0H10C4.5,0,0,4.5,0,10v47.6c0,5.5,4.5,10,10,10h38.2v0l9.7,0l9.7,9.7V10C67.6,4.5,63.1,0,57.6,0z"/>
  <rect x="16.2" y="19.6" fill="#FFFFFF" width="3" height="28.5"/>
  <path fill="#FFFFFF" d="M40.9,45.7c4.1,0,7.8-2.1,9.9-5.3l2.6,1.7c-2.7,4-7.3,6.7-12.5,6.7c-8.3,0-15-6.7-15-15c0-8.3,6.7-15,15-15
    c5.2,0,9.8,2.7,12.5,6.7l-2.6,1.7c-2.1-3.2-5.8-5.3-9.9-5.3c-6.6,0-11.9,5.3-11.9,11.9S34.4,45.7,40.9,45.7z"/>

 <!-- Vampire elements -->
  <ellipse fill="#4C001F" cx="12.8" cy="32.7" rx="3.2" ry="3.2"/>
  <ellipse fill="#4C001F" cx="54.6" cy="32.7" rx="3.2" ry="3.2"/>
  <g>
    <polygon fill="#4C001F" points="58,46.6 56,49 11.7,49 9.7,46.6"/>
    <polygon fill="#4C001F" points="22.7,46.7 16.1,54.1 9.7,46.6"/>
    <polygon fill="#4C001F" points="56.2,48.8 51.8,54.1 47.3,48.8"/>
  </g>
  <polygon fill="#FFFFFF" points="54.8,15 58.2,18.4 47.9,28.6 44.4,25.2"/>
  <polygon fill="#FFFFFF" points="10,18.4 13.5,15 23.9,25.2 20.4,28.6"/>
</g>
</svg>

Hiding Paths

Since we only want to see the original logo and only want the vampire paths included to be morphed into, I will hide them. I do this using two classes.

.anim-hide and .anim-fade

The first class (.anim-hide) has visibility: hidden. This is for paths that should never be visible but are only there for the dimensions of what to merge your original path into.

The second class (.anim-fade) has opacity: 0. Since I do not want to morph every path I have, I am going to use this class to allow me to just fade some paths in when necessary.

 

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     viewBox="0 0 67.6 77.3" enable-background="new 0 0 67.6 77.3" xml:space="preserve">
  <g>
    <path fill="#759ed0" d="M57.6,0H10C4.5,0,0,4.5,0,10v47.6c0,5.5,4.5,10,10,10h38.2v0l9.7,0l9.7,9.7V10C67.6,4.5,63.1,0,57.6,0z"/>
    <rect x="16.2" y="19.6" fill="#FFFFFF" width="3" height="28.5"/>
    <path fill="#FFFFFF" d="M40.9,45.7c4.1,0,7.8-2.1,9.9-5.3l2.6,1.7c-2.7,4-7.3,6.7-12.5,6.7c-8.3,0-15-6.7-15-15c0-8.3,6.7-15,15-15
      c5.2,0,9.8,2.7,12.5,6.7l-2.6,1.7c-2.1-3.2-5.8-5.3-9.9-5.3c-6.6,0-11.9,5.3-11.9,11.9S34.4,45.7,40.9,45.7z"/>

    <ellipse class="anim-fade" fill="#4C001F" cx="12.8" cy="32.7" rx="3.2" ry="3.2"/>
    <ellipse class="anim-fade" fill="#4C001F" cx="54.6" cy="32.7" rx="3.2" ry="3.2"/>
    <g>
      <polygon class="anim-fade" fill="#4C001F" points="58,46.6 56,49 11.7,49 9.7,46.6    "/>
      <polygon class="anim-fade" fill="#4C001F" points="22.7,46.7 16.1,54.1 9.7,46.6    "/>
      <polygon class="anim-fade" fill="#4C001F" points="56.2,48.8 51.8,54.1 47.3,48.8     "/>
    </g>
    <polygon class="anim-hide" fill="#FFFFFF" points="54.8,15 58.2,18.4 47.9,28.6 44.4,25.2  "/>
    <polygon class="anim-hide" fill="#ffffff" points="10,18.4 13.5,15 23.9,25.2 20.4,28.6   "/>
  </g>
</svg>

Unique Identifiers

Next we need to go through each element that we actually want to morph and give it a unique identifier so that we can trigger it in our JavaScript. We should also name our SVG itself.

 

<svg class="ic-logo" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     viewBox="0 0 67.6 77.3" enable-background="new 0 0 67.6 77.3" xml:space="preserve">
  <g>
    <path class="logo-chicklet" fill="#759ed0" d="M57.6,0H10C4.5,0,0,4.5,0,10v47.6c0,5.5,4.5,10,10,10h38.2v0l9.7,0l9.7,9.7V10C67.6,4.5,63.1,0,57.6,0z"/>
    <rect class="logo-i" x="16.2" y="19.6" fill="#FFFFFF" width="3" height="28.5"/>
    <path class="logo-c" fill="#FFFFFF" d="M40.9,45.7c4.1,0,7.8-2.1,9.9-5.3l2.6,1.7c-2.7,4-7.3,6.7-12.5,6.7c-8.3,0-15-6.7-15-15c0-8.3,6.7-15,15-15
      c5.2,0,9.8,2.7,12.5,6.7l-2.6,1.7c-2.1-3.2-5.8-5.3-9.9-5.3c-6.6,0-11.9,5.3-11.9,11.9S34.4,45.7,40.9,45.7z"/>

    <ellipse class="anim-fade" fill="#4C001F" cx="12.8" cy="32.7" rx="3.2" ry="3.2"/>
    <ellipse class="anim-fade" fill="#4C001F" cx="54.6" cy="32.7" rx="3.2" ry="3.2"/>
    <g>
      <polygon class="anim-fade" fill="#4C001F" points="58,46.6 56,49 11.7,49 9.7,46.6    "/>
      <polygon class="anim-fade" fill="#4C001F" points="22.7,46.7 16.1,54.1 9.7,46.6    "/>
      <polygon class="anim-fade" fill="#4C001F" points="56.2,48.8 51.8,54.1 47.3,48.8     "/>
    </g>
    <polygon class="vamp-right-eyebrow anim-hide" fill="#FFFFFF" points="54.8,15 58.2,18.4 47.9,28.6 44.4,25.2  "/>
    <polygon class="vamp-left-eyebrow anim-hide" fill="#ffffff" points="10,18.4 13.5,15 23.9,25.2 20.4,28.6   "/>
  </g>
</svg>

Now that we are only seeing the paths we want and everything is named, it is time to do some morphing!

Morphing Time!

Since there is some really cool stuff happening in our JavaScript, let’s break it down into sections and look at what is happening.

var mainSVG = document.querySelector('.ic-logo');

We get the SVG. This is used later for handling our click events.

var faceTl = new TimelineMax({paused:true});

Initialize the timeline.

MorphSVGPlugin.convertToPath("circle, rect, ellipse, line, polygon, polyline");

Now this is interesting! Here we are actually taking all of our elements that are not of type “path” and converting them into paths without losing any quality. This is extremely powerful and allows MorphSVG to animate all SVG elements.


faceTl.to('.logo-i', 0.75, {
  morphSVG:{shape:'.vamp-left-eyebrow'},
  ease:Elastic.easeInOut
})
.to('.logo-c', 0.75, {
  morphSVG:{shape:'.vamp-right-eyebrow'},
  ease:Elastic.easeInOut
}, '-=0.75')
.to('.anim-fade', 0.5, {
    opacity: 1,
    ease:Power2.easeInOut
}, '-=0.5')
.to('.logo-chicklet', 0.75, {
  fill: '#D22D78'
}, '-=0.75')
.to(document.body, 0.75, {
  backgroundColor:'#000000',
  ease:Power2.easeInOut
}, '-=0.75');

Above is our actual timeline. The .to method takes 4 parameters here:

  • Path Name
  • Animation Duration
  • An object of animations
  • An offset time

The most important of these is our animation object. You will see properties like:

morphSVG:{shape:'.vamp-right-eyebrow'}

This is what actually merges the path into one of your hidden paths in your SVG.

Now the only thing left to do is click play.


mainSVG.addEventListener('click', function() {
  if (faceTl.time() > 0) {
    faceTl.reverse();
  } else {
    faceTl.play(0);
  }
});

We set up a simple click event that plays forward or backward depending on the state of the timeline. And just like that, we can now scare the crap out of anyone who clicks on our logo (or at least maybe get a smile).

For more on SVGMorph, check out the links below:
http://greensock.com/morphSVG
http://codepen.io/collection/naMaNQ/

About the Author

Image of Ashton Harris