SVG Morph

The component that covers SVG morphing, an animation that's close to impossible with CSS3 transitions and not supported in some legacy browsers. It comes packed with tools and options to improve performance and visual presentation.

Overview

Animate SVG paths with line-to path commands, improve visual presentation and optimize performance on any device.

The KUTE.js SVG Morph component enables animation for the d (description) presentation attribute and is one of the most important in all the SVG components.

It only works with inline SVGPathElement shapes and the presentation is always the same when shapes are closed or not (their d attribute ends with Z path command). On initialization or animation start, depending on the chosen KUTE.js method, it will sample a number of points along the two paths based on a default / given sample size and will create two arrays of coordinates we need for interpolation.

This component was originally inspired by a D3.js path morphing example and now implements a set of D3 polygon geometric operations and other functionalities from flubber to produce the coordinates for a very consistent morphing animation.

While in some cases you might be able to create SVG morphing animations via CSS3 transition, this component was developed to provide various solutions for working with complex shapes, bringing convenience, resources and clarity to one of the most complex types of animation.

All path processing is powered by our SVGPathCommander starting KUTE.js version 2.0.14, which aims to modernize and improve the path processing and enable you to prepare your path strings in Node.js.

Options

The easy way to optimize morphing animation for every device in a single option.

The SVG Morph component comes with a simple option to optimize animation on every device. Previous versions used to have additional options required for processing, but now the component will handle all that for you.

  • morphPrecision: Number option allows you to set the sampling size of the shapes in pixels. The lesser value the better visual but the more power consumption and less performance. The default value is 10 but the processing functions will determine the best possible outcome depending on shapes' path commands.

Basic Example

The first morphing animation example is a transition from a rectangle into a star, the first path is the start shape and the second is the end shape; we can also add some ID to the paths so we can easily target them with our code.

<svg id="morph-example" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600">
  <path id="rectangle" d="M38.01,5.653h526.531c17.905,0,32.422,14.516,32.422,32.422v526.531 c0,17.905-14.517,32.422-32.422,32.422H38.01c-17.906,0-32.422-14.517-32.422-32.422V38.075C5.588,20.169,20.104,5.653,38.01,5.653z"/>
  <path id="star" style="visibility:hidden" d="M301.113,12.011l99.25,179.996l201.864,38.778L461.706,380.808 l25.508,203.958l-186.101-87.287L115.01,584.766l25.507-203.958L0,230.785l201.86-38.778L301.113,12.011"/>
</svg>

Now we can apply both .to() and fromTo() methods:

// the fromTo() method
var tween = KUTE.fromTo('#rectangle', {path: '#rectangle' }, { path: '#star' }).start();
// OR
// the to() method will take the path's d attribute value and use it as start value
var tween = KUTE.to('#rectangle', { path: '#star' }).start();
// OR
// simply pass in a valid path string without the need to have two paths in your SVG
var tween = KUTE.to('#rectangle', { path: 'M301.113,12.011l99.25,179.996l201.864,38.778L461.706,380.808l25.508,203.958l-186.101-87.287L115.01,584.766l25.507-203.958L0,230.785l201.86-38.778L301.113,12.011' }).start();

For all the above tween objects the animation should look like this:

That's it, let move on to the next example.

Morphing Unclosed Shapes To Closed Shapes

The next morphing animation example is a transition from a line into a circle and this example showcases the component's behavior when one of the paths is not closed (it doesn't have the Z path command), while the other is closed. In all cases, the component will always consider shapes to be closed, so let's have a look at the example:

<svg id="morph-example" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600">
  <path id="line" fill="transparent" stroke="orange" stroke-linejoin="round" stroke-width="10" d="M10 300L590 300"/>
  <path id="circle" style="visibility:hidden" d="M10,300a290,290 0 1,0 580,0a290,290 0 1,0 -580,0z"/>
</svg>

Now we can apply both .to() and fromTo() methods:

// the fromTo() method
var tween = KUTE.fromTo('#line', {path: '#line' }, { path: '#circle' }).start();
// OR
// the to() method will take the path's d attribute value and use it as start value
var tween = KUTE.to('#line', { path: '#circle' }).start();

The functionality of this component is different from the svgCubicMorph component in the sense that it will animate the shapes as if they are both and always closed. In the above example, the orange line is closed while the green line is not, and the animation is the same.

Polygon Paths

When your paths are only lineto, vertical-lineto and horizontal-lineto based shapes (the d attribute consists of only L, V and H path commands), the SVG Morph component will work differently from previous versions, it will sample points as for any other non-polygon shapes. In this case you can set a higher morphPrecision value to optimize performance.

// let's morph a triangle into a star
var tween1 = KUTE.to('#triangle', { path: '#star' }).start();

// or same path into a square
var tween2 = KUTE.to('#triangle', { path: '#square' }).start();

Did you catch the cat?

Subpath Example

In other cases, you may want to morph paths that have subpaths. Let's have a look at the following paths:

<svg id="multi-morph-example" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
  <path id="w1" d="M412.23 511.914c-47.708-24.518-94.086-36.958-137.88-36.958-5.956 0-11.952 0.18-17.948 0.708-55.88 4.624-106.922 19.368-139.75 30.828-8.708 3.198-17.634 6.576-26.83 10.306l-89.822 311.394c61.702-22.832 116.292-33.938 166.27-33.938 80.846 0 139.528 30.208 187.992 61.304 22.962-77.918 78.044-266.09 94.482-322.324-11.95-7.284-24.076-14.57-36.514-21.32z 
  m116.118 79.156l-90.446 314.148c26.832 15.372 117.098 64.05 186.212 64.05 55.792 0 118.252-14.296 190.834-43.792l86.356-301.976c-58.632 18.922-114.876 28.52-167.464 28.52-95.95 0-163.114-31.098-205.492-60.95z 
  m-235.526-222.28c77.118 0.798 134.152 30.208 181.416 60.502l92.752-317.344c-19.546-11.196-70.806-39.094-107.858-48.6-24.386-5.684-50.02-8.616-77.204-8.616-51.796 0.976-108.388 13.946-172.888 39.8l-88.44 310.596c64.808-24.436 120.644-36.34 172.086-36.34 0.046 0.002 0.136 0.002 0.136 0.002z 
  m731.178-170.666c-58.814 22.832-116.208 34.466-171.028 34.466-91.686 0-159.292-31.802-203.094-62.366l-91.95 318.236c61.746 39.708 128.29 59.878 198.122 59.878 56.948 0 115.94-13.68 175.462-40.688l-0.182-2.222 3.734-0.886 88.936-306.418z"/>
  <path id="w2" d="M0 187.396l367.2-50.6v354.798h-367.2v-304.2z 
  m0 649.2v-299.798h367.2v350.398z 
  m407.6 56v-355.798h488.4v423.2z 
  m0-761.2l488.4-67.4v427.6h-488.4v-360.2z"/>
</svg>

As you can see, both these paths have subpaths, and this component will only animate the first subpath from both paths. To animate them all there are a few easy steps required in preparation for the animation, so here's a quick guide:

  1. Use the SVGPathCommander demo page to split your path string into multiple sub-path strings. It's important to do this to avoid inconsistencies with shapes having relative sub-path commands. You can also use the utility to reverse paths if required.
  2. Create a new <path> shape for each sub-path string from M to Z path commands. See the sample code below.
  3. Give the new paths an id="uniqueID" attribute so you can target them easily. You could use relevant namespace to help you better understand positioning. EG: id="square-top-left" or id="left-eye"
  4. In the browser console inspect with your mouse all paths from both starting and ending shapes and determine which shapes should morph to which. The idea is to produce a morphing animation where points from each shape travel the least possible distance, however this is where you can get creative, as shown in one of the examples below.
  5. If the number of the starting and ending shapes are not equal, you can consider either duplicating one of the subpath shapes close to it's corresponding subpath shape or creating a sample shape close to the corresponding subpath shape.
  6. Update the id attribute for all starting and ending shapes to match positions and make it easier to work with the tween objects.
  7. Optional: set a fill attribute for each new shape if you like, normally you coulnd't have done it with the original paths.
  8. Create your tween objects and get to animating and tweaking.

For our example here, this is the end result markup for the shapes to be used for morphing animation:

<svg id="multi-morph-example" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 550 550">
  <path id="w11" d="M206.115,255.957c-23.854-12.259-47.043-18.479-68.94-18.479c-2.978,0-5.976,0.09-8.974,0.354 c-27.94,2.312-53.461,9.684-69.875,15.414c-4.354,1.599-8.817,3.288-13.415,5.152L0,414.096 c30.851-11.416,58.146-16.969,83.135-16.969c40.423,0,69.764,15.104,93.996,30.652c11.481-38.959,39.022-133.045,47.241-161.162 C218.397,262.975,212.334,259.332,206.115,255.957z"/>
  <path id="w12" d="M264.174,295.535l-45.223,157.074c13.416,7.686,58.549,32.024,93.105,32.024 c27.896,0,59.127-7.147,95.417-21.896l43.179-150.988c-29.316,9.461-57.438,14.26-83.732,14.26 C318.945,326.01,285.363,310.461,264.174,295.535z"/>
  <path id="w13" d="M146.411,184.395c38.559,0.399,67.076,15.104,90.708,30.251l46.376-158.672c-9.772-5.598-35.403-19.547-53.929-24.3c-12.193-2.842-25.01-4.308-38.602-4.308c-25.898,0.488-54.194,6.973-86.444,19.9 L60.3,202.564c32.404-12.218,60.322-18.17,86.043-18.17C146.366,184.395,146.411,184.395,146.411,184.395L146.411,184.395z"/>
  <path id="w14" d="M512,99.062c-29.407,11.416-58.104,17.233-85.514,17.233c-45.844,0-79.646-15.901-101.547-31.183L278.964,244.23 c30.873,19.854,64.146,29.939,99.062,29.939c28.474,0,57.97-6.84,87.73-20.344l-0.091-1.111l1.867-0.443L512,99.062z"/>

  <path id="w21" style="visibility:hidden" d="M192 471.918l-191.844-26.297-0.010-157.621h191.854z"/>
  <path id="w22" style="visibility:hidden" d="M479.999 288l-0.063 224-255.936-36.008v-187.992z"/>
  <path id="w23" style="visibility:hidden" d="M0.175 256l-0.175-156.037 192-26.072v182.109z"/>
  <path id="w24" style="visibility:hidden" d="M224 69.241l255.936-37.241v224h-255.936z"/>
</svg>

The graphic on the left side of the below example is exactly for the above markup, no option needed out of the box nearly perfect animation, while the right side example showcases a different or perhaps more creative example of this morph animation:

As you can imagine, it's quite hard if not impossible to code something that would do all this work automatically. Perhaps in the future we could have dedicated AI powered APIs to train and program for this work, but until then, it's up to you to learn, observe, adapt and tweak in order to get the most out of this component.

Intersecting Paths Example

The last morph example is a bit more complex as the paths have intersecting subpaths with different positions as well as different amounts of subpaths. In this case you can manually clone one or more subpaths in a way that the number of starting shapes is equal to the number of ending shapes, as well as making sure the starting shapes are close to their corresponding ending shapes; at this point you should be just like in the previous examples.

You need to inspect the markup here in your browser console to get an idea on how the shapes have been arranged, we have used a <mask> where we included the subpaths of both start and end shape, just to get the same visual as the original paths.

The example on the left side showcases the cloning of one of the shapes to match the amount of starting and ending shapes, while the right side showcases using a sample shape, somewhere close to its corresponding end shape, a much better animation, but in the end, it's up to you and your need on preparing, analyzing as well as deciding on how to optimize these cases.

Notes