September 27, 2021

Morphing SVG paths

I was just learning more about SVG manipulation and animation and came across SVG path morphing. This looked way easier than I thought so I wanted to have a go! See CSS-tricks : The many tools for Shape-morphing – there are loads of tools for this so use which one you want and fits best in your stack. As I am deepening my react skills I decided to follow CSS-tricks : Morphine SVG with react-spring.

Preparing SVGs for morphing

The code part is really simple, follow the tutorial and find more resources at React-Spring. The tutorials and examples worked great for me, however I had some real difficulties preparing my shapes for morphing, and could not find any good material online on creating / preparing SVG’s for morphing yourself, so I wanted to write up the methods I found.

If you know better methods or improvements please let me know

Inkscape

There is probably some tools out there that do a lot of this for you, but I think you will always end up having to modify a little, and even small modifications seemed to mess up my file formatting, so I reckon some of this will come in handy. Plus, there is a certain art to this. As describe in the CSS-tricks tutorial – the order and placements of all points will affect the animation. I used Inkscape as I have used it before and it is free – so I am documenting how to do this all in Inkscape v1.1

SVG path syntax

The inconvenient truth is it would be helpful to know some SVG path syntax to know whats going wrong. A few little details about it to help you figure out problems in creating matching morph paths. I found everything I needed to know in this CSS-tricks : SVG path syntax guide.

So grab an svg file, or create a new on with inkscape, and open the file in a text editor

Absolute and relative coordinates

The main problem I had is that inkscape optimises the files it stores. There are many different way (in SVG code) to draw a square and depending on your settings and how you create this inkscape might save it differently. By default Inkscape will optimise this in smart ways, which is probably great, but changes the way the paths are recorded. Itcan optimise the two shapes differently and thus create discrepancies even when you meticulously made them match.

1. Use Relative coordinates

The first most important step is to store paths in relative coordinates. These are way more useful and flexible in general.
Generally UPPER CASE is absolute, lower case is relative. E.g.
L 10,10 will draw a line from current position to Point [10,10]
l 10,10 will draw a line from current position adding [X+10, Y+10] relatively from current position

  1. Go to Inkscape / Edit / Preferences ( Ctrl + Shift + P )
  2. Go to ‘Input / Output’ / ‘SVG Output’
  3. Here under Path data, select path string format : ‘Relative’

The Errors

The morph function needs the two shapes to have the same type of nodes, it can not automatically convert between the different node / line types, you need to do this for it. Here are two types of errors, I’ll explain them so you recognize them but they are both fixed the same way. (see after)

Node Types – Straight lines

Straight Lines – when you are drawing a straight lines this is ‘l X,Y‘ in SVG code. However if this lines is horizontal or vertical this would be ‘l X,0‘ or ‘l 0,Y‘ and the SVG code is automatically shortened to ‘h X‘ and ‘v Y‘ respectively to save space (despite relative coordinates settings)

Example – the following two shapes will give you

Square :
d="m 21.712793,32.569189 h 51.955615 v 55.057442 h -52.343343 z"

Diamond
d="m 122.52219,38.385117 l 5.81593,-6.203656 l 52.34334,45.751958 l -5.81593,7.366842 z"

Error : Node Types

Next, it will have problems morphing a curve into a linear line, and you will get glitchy results, as curve segments have 2 extra points for the handles, for example :

Gives you :
d="m 48.466057,12.019582 l 30.242819,32.181463 l -32.956918,25.977805 l -25.977807,-30.630548 z"
and
d="m 154.31593,12.795039 c 18.22323,0 27.91644,14.733682 27.14099,30.242818 c -0.77546,15.50914 -8.1423,24.814623 -29.07964,24.814623 c -20.93734,0 -28.30417,-12.40731 -28.6919,-26.365536 c -0.38773,-13.958223 15.50913,-29.079633 30.63055,-28.691905 z"

Solution

Both of these are easily fixed at the same time, by turning all nodes into curves.

1. Use the node selector (N) : ‘Edit paths by nodes’

Then select all the nodes of the shapes ( Ctrl + A ) and click the little icon in the top bar for ‘Make selected segments curves‘ . This turns line segments into curves with straight handles.

Now gives us :
 d="m 48.466057,12.019582 c 10.08094,10.727154 20.161879,21.454309 30.242819,32.181463 c -10.985639,8.659268 -21.971279,17.318537 -32.956918,25.977805 c -8.659269,-10.210183 -17.318538,-20.420365 -25.977807,-30.630548 c 9.563969,-9.17624 19.127937,-18.35248 28.691906,-27.52872 z"
and
 d="m 154.31593,12.795039 c 18.22323,0 27.91644,14.733682 27.14099,30.242818 c -0.77546,15.50914 -8.1423,24.814623 -29.07964,24.814623 c -20.93734,0 -28.30417,-12.40731 -28.6919,-26.365536 c -0.38773,-13.958223 15.50913,-29.079633 30.63055,-28.691905 z"

Problems

If this Bar is not visible for you, go to
Inkscape > View > 'Show/Hide' > 'Tool Controls Bar'

Error : Number of Nodes

The other error is a little trickier in some case. The two morphing shapes need to have the same number of nodes. The morph tool simply moves node 1 of shape A in equal steps to node 1 of shape B. Doing the same with node 2, 3, 4 … etc.
When shapes have an unequal number of nodes you need to insert extra nodes, so that each node has a partner.

This is simple enough, select the segment where you want to add a node, i.e. the two nodes between which you want an extra segment. ( To select multiple, just use a mouse area-drag, or hold down shift and click to select the ones you need )
Then click ‘insert new nodes into selected segments

Node order

Now as other tutorials will tell you, bear in mind the node order, and insert extra nodes in a similar area / order where the other shape has more nodes.

E.g. here adding extra nodes at the top will create a less fluid effect. This is also your best tool to affect the style of the animation, so try adding nodes in different places to see the effects.

First / Last Node

When creating your own shapes you will know where the first / last node is. However with complete shapes and imported SVGs you can get closed shapes and won’t know where this is. Just select the node you want as your first/last node and select ‘Break path at selected nodes’.

Solution 2 – Interpolation

Inkscape has a feature that is almost like morphing called interpolation. This will morph from one shape to another creating intermittent paths in between. This is a good option if you don’t want to do it all by hand. You no longer need to worry about the number of nodes and their placement, but should still make sure afterwards that you ‘convert all segments to curves‘ as above.

To use Interpolation :
More detail on interpolation and settings, check out the inkscape tutorial.
Use the Inkscape Interpolation extension, go to :
Inkscape / Extensions / 'Generate from Path' / 'Interpolateā€¦'
Exponent : ‘0’ gives you linear interpolation between the two
Interpolation steps : reduce to 1 as you don’t need them , but its good to have one to see if it looks alright or not at all as you imagined
Interpolation method : ‘1’ is better for linear / line paths, method ‘2’ is better for round shapes / curve paths
Make sure you tick : ‘Duplicate Endpaths’ – as this is what we are after

This will create a new group of objects morphing from one to the other. The first and last of these are copies of the original shapes, but with added nodes to allow interpolation, and luckily for you, also morphing.

Format and Sizing

Finally. How to get the right data out

  1. I like to keep both shapes in the same file, it makes life easier
  2. Place them in top of each other, or the shape will move when it is morphing
  3. Move to X: 0 , Y: 0

Scale

In the inkscape vector file you will find something like :
<svg ... >
...
<path
...
d="m .... z"
... />
...
</svg>

We are only copying out the path data of d=”…” , whereas the <svg .. > tag contains all the document properties. So I like to just always use a size of 100 x 100 . The size and viewbox are attributes in the <svg …> tag. By using 100 we don’t need to copy this out. So I resized the shapes above to a width of 100

So we can just set the height in JSX <svg> element as :

<svg    
width="100mm"            
height="100mm"            
viewBox="0 0 100 100"   >        
<animated.path

d=”…”
/>
</svg>

.