Morphing one SVG into another

754 views
Skip to first unread message

Ivan Horvatin

unread,
Oct 13, 2015, 7:00:57 AM10/13/15
to Snapsvg
Hey,
me and my friend decided to do an SVG animation but we came to a stump.
We have a couple of SVG pictures made from polygons (triangles) and each one of them has its own gradient.
We want to morph those polygons and gradients into the other ones in the other SVG. Is it possible to do that
with Snapsvg? I tried using SMIL to do it, but its a lot of manual work since we have around 650 polygons and gradients
for each image and its a pain to use <animate> and <animateTransform>.

What we are trying to do is morph all those polygons from one image into the other, so we get a nice transformation path for each triangle,
and do that for a couple of pictures in a row.
For example we would have 4 Avanger characters made with triangles and then on click of the first he would transform into the second one, 
on click on second into third, and so on. 

Can it be done with Snap.svg and can you offer me some guidelines? 
Any help would be greatly appriciated!

Thanks in advance

Ian

unread,
Oct 13, 2015, 7:19:41 AM10/13/15
to Snapsvg
For morphing, ideally you need paths to morph from and to that have the same number of points. 

If you have 2 arbitrary shapes and want to animate between them, you could interpolate between each attribute, assuming you have the same attributes in each, but you would need a bit more work to loop through those attributes and interpolate. I think it should be ok.

It may help if you put a couple of examples on a jsfiddle.

If you are trying to interpolate between 2 different objects (eg a circle to a rect), it will likely not work unless you can convert it into a path with the same number of points.

Ivan Horvatin

unread,
Oct 13, 2015, 8:30:51 AM10/13/15
to Snapsvg
Thanks for the info, i only have polygons as triangles, all with the same amount of points, the only problem is the gradients, they tend to deviate with the number of offsets in them.
The whole idea is to rearrange the triangles and change the offsets to match the ones in another image. That is also SVG, so i know my start point and my end point. 
This is the link to fiddle where i have manually, copy/pasted things i needed for it to animate. And it works like i imagined it, however, it takes too much time, and generates too much code,
so i was hoping if i could do it with a library/plugin like Snapsvg

Here is the link to jfiddle:


This is what i did, using SMIL, but the code is huge, and its mostly same. But you get the idea..i want to do these kind of animations with multiple characters, interchanging one into the other.

Ivan Horvatin

unread,
Oct 13, 2015, 8:32:11 AM10/13/15
to Snapsvg
Its gonna take a while to open since the code is kinda huge...

Ian

unread,
Oct 13, 2015, 8:46:41 AM10/13/15
to Snapsvg
hmm I'm not convinced you will see any performance improvement in Snap over that tbh, I would expect it to be the other way around if anything in this particular case.

If you can simplify a test case providing a start and end svg that has the same number and type of elements (eg just with 3 triangles or something), it may be quicker to figure out though,

Ivan Horvatin

unread,
Oct 13, 2015, 8:55:42 AM10/13/15
to Snapsvg
https://jsfiddle.net/dmdz6tsv/1/

Ok, how about this?
I just cut out most of the polygons out.
Its not just about the performance, it doesnt need to be that better, when it loads it looks ok by itself. Im having trouble figuring a way to code it so it doesnt have so much code,
or just an easier way to animate it. 
Because allthough it works like this, and looks good to me i had to copy paste and change a lot of files manually, and it took me  too much time. I couldnt come up with a more simpler way at that point.
So im searching for ideas, libraries, plugins that would maybe come to solution with which i could acomplish this type of animation.

Ian

unread,
Oct 13, 2015, 8:58:45 AM10/13/15
to Snapsvg
I mean, do you have them as 2 separate SVGs to interpolate between, just a few polygons/gradients. 

Ivan Horvatin

unread,
Oct 13, 2015, 9:10:01 AM10/13/15
to Snapsvg
This is one SVG



This is the other SVG


I just left 10 points in each just for the example

Ivan Horvatin

unread,
Oct 14, 2015, 8:47:06 AM10/14/15
to Snapsvg
So..Ian, do you think i could somehow use Snap.svg to irretate through these polygons and gradients and animate them automaticly?
Making them travel from the first svg set of coordinates to the second. And then after making the second svg travel to the thrid, and so on?

Ian

unread,
Oct 14, 2015, 9:06:36 AM10/14/15
to Snapsvg
I did have a quick look at it, and I think it may be possible to iterate through each element points and interpolate between to the value in the 2nd svg, however, with the linear gradient I'm not sure, as that seems to vary in elements as well as values, so not sure what you would do there.

I also suspect there would be a loss in performance. So I guess the question is if you want to try and write the code to find out. I personally don't think the end result would be great, but its up to you if you wanted to try,

Ivan Horvatin

unread,
Oct 14, 2015, 9:35:53 AM10/14/15
to Snapsvg
I could always add the gradient elements so both svg's have the same number of them. Like adding "imaginary points" that actually dont change anything but just fill the space where there is less elements. That would make the interpolation possible, right?
I know that probably the performance wont be as good, but if you take into account that i had to copy/paste code for each of those polygons and gradients and thereby increase the code from around 7,5k lines to 15k lines...i think its a bit too much..

Ian

unread,
Oct 14, 2015, 9:39:47 AM10/14/15
to Snapsvg
The other option I guess could be to programmatically create the markup for the animation as a one off, if the problem is repeating the process for lots of svgs. That would be an interesting option, not sure how difficult though, I've not tried something like that before.

Ivan Horvatin

unread,
Oct 14, 2015, 9:41:02 AM10/14/15
to Snapsvg
Could you explain a bit what you mean?

Ian

unread,
Oct 14, 2015, 9:52:07 AM10/14/15
to Snapsvg
I suppose the thing is, I don't know how you created the animation markup, for example...

            <animate attributeName="x1" values="382.77;193.82" begin="0.7" dur="4s" fill="freeze" calcMode="spline" keySplines="0.42 0 1 1" keyTimes="0;1"></animate>
            <animate attributeName="y1" values="190.65;201.77" begin="0.7" dur="4s" fill="freeze" calcMode="spline" keySplines="0.42 0 1 1" keyTimes="0;1"></animate>
            <animate attributeName="x2" values="398.98;211.11" begin="0.7" dur="4s" fill="freeze" calcMode="spline" keySplines="0.42 0 1 1" keyTimes="0;1"></animate>
            <animate attributeName="y2" values="190.65;204.02" begin="0.7" dur="4s" fill="freeze" calcMode="spline" keySplines="0.42 0 1 1" keyTimes="0;1"></animate>

Take something like the above, the begin/duration,keysplines etc are probably quite common throughout the code. Did you copy paste them there, did some other animation app create that markup, or something else.

I was just pondering whether given 2 elements (svg's) attributes and known start and end values, if you could calculate/create what that markup would be.


Ian

unread,
Oct 14, 2015, 9:54:03 AM10/14/15
to Snapsvg
Its also worth bearing in mind, I don't think IE has SMIL support, but I may be wrong.

Ivan Horvatin

unread,
Oct 14, 2015, 10:04:01 AM10/14/15
to Snapsvg
I created the animation markup and then copy pasted it throughout the code because i wanted to see if it will work, and how it will work. But was thinking of changing the start time to a random number between 0.5-2 seconds or so. Used the keysplines so it would seem as an ease in animation. But yeah, i created the first one, then copied the values to the others. And i think you are right, IE does not support SMIL...

Ivan Horvatin

unread,
Oct 16, 2015, 5:35:26 AM10/16/15
to Snapsvg
Hey,
I stumbled upon some code, that seems to me is yours, some kind of tutorial for Snap.svg. http://svg.dabbles.info/snaptut-loadmulti-callback

Is it ok if i use this tutorial to load multiple SVG's?

Also, can i use animate function to use the points of another SVG  as its destination. Something like animate(points of element from svg1, points of element from svg2)?
And can it be done with all the points at the same time (asynchronius)? Or will i have to write an animate statement for each element?

Ivan Horvatin

unread,
Oct 16, 2015, 5:37:56 AM10/16/15
to Snapsvg
I am kind of a begginer, so im sorry if dont know how to explain something well, or ask really basic questions

Ian

unread,
Oct 16, 2015, 5:43:09 AM10/16/15
to Snapsvg
Sure, use what you want on there.

You could possibly use the Snap,animate function and pass it the start and end values, eg

Snap.animate( svg1Val1, svg2Val1, function( interpolatedVal ) {
  svg1SomeElement.attr( { someAttr:  interpolatedVal } )
}, 2000)

Thats pseudocode, but if you pass it the start and end values, Snap animate will interpolate between them. So you possibly could use this route.

I'm not sure if you can animate all the points in one go like that, you may need to do it on a pointx an pointy basis, not quite sure.

You may even be able to do something like...

svg1Element.animate({ someAttr: svg2Element.attr('someAttr') }, 2000) or similar if you don't need to break it down point by point

Ivan Horvatin

unread,
Oct 16, 2015, 7:23:05 AM10/16/15
to Snapsvg
oh, so for example in this last piece you wrote. svg1Element.animate({ someAttr: svg2Element.attr('someAttr') }, 2000)

I could lets say have polygon with X points. And i would just use that piece in the code like svg1polygon(1).animate ( { points:svg2polygon(1).attr('points') }, 2000 )
by (1) i mean the first polygon in the svg out of X amount of polygons.
I dont need to break it in points, just need to interpolate between polygons and gradients like mentioned above. Preferably all at the same time, if its possible.

Ivan Horvatin

unread,
Oct 16, 2015, 7:23:53 AM10/16/15
to Snapsvg
And im really thankfull for all your insight and help!

Ian

unread,
Oct 16, 2015, 7:27:30 AM10/16/15
to Snapsvg
You can try it, I'm not sure if Snap will interpolate a whole set of points like that. I think it does for path strings, but not sure for something like this. Try and see!

Ivan Horvatin

unread,
Oct 16, 2015, 9:25:12 AM10/16/15
to Snapsvg
Will do, ill post some results tomorrow if i manage to do something!

Ivan Horvatin

unread,
Oct 18, 2015, 1:38:00 PM10/18/15
to Snapsvg
How would i load 2 svgs, or more and then iterate over elements so i can morph one into the other?

Would i go like this? 

var s = Snap("#svgout");
var svg1 = Snap.load ("test1.svg", function());
var svg2 = Snap.load ("test2.svg", function());

var anim = (svg1.animate ( { points:svg2.attr('points') }, 2000 );

s.append(anim);

Or something different?

Ivan Horvatin

unread,
Oct 18, 2015, 3:17:45 PM10/18/15
to Snapsvg
<!DOCTYPE html>
    <head>
        <meta charset="utf-8">
        <title>Testing</title>
    </head>
    <body>
      <div><svg id="svgout"></svg></div>
    </body>
    <script src="Snap.svg-0.4.1/dist/snap.svg-min.js"></script>
    <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
     <script>
        var s = Snap("#svgout");
var g = s.group();
var svg1 = Snap.load("test1.svg", function(loadFragment1){
                                                g.append( loadedFragment1 );
                                                g.hover( anim );
                                        });
var svg2 = Snap.load("test2.svg", function());

var anim = function() {svg1.animate ( { points:svg2.attr('points') }, 2000 )};

s.append(anim);
    </script>
</html>



I keep getting  Uncaught SyntaxError: Unexpected token )   on line 18.. that is var svg2 = Snap.load("test2.svg", function()); line... but i dont see anything wrong with the syntax..
maybe im doing something wrong?

Ian

unread,
Oct 18, 2015, 4:08:38 PM10/18/15
to Snapsvg
don't pass function() as an argument (unless it returns a function), pass function...

eg Snap.load(file, function) NOT Snap.load(file, function() );

In the previous example, I think you will find you need to append the fragment before you can do things like animate it.

Ivan Horvatin

unread,
Oct 19, 2015, 2:23:22 AM10/19/15
to Snapsvg
Wont, appending draw the fragments right away?
I need only one to be drawn and the other to act as the destination animation.

Ian

unread,
Oct 19, 2015, 3:49:19 AM10/19/15
to Snapsvg
Yes, appending it will add straight away. So if you want the first image to animate to another, you will need to append that, and animate it. You don't need the animate method on the 2nd image, so you could just use selects on it without appending probably. I did misread the code a bit and was just looking at the later append though, so part of my initial thoughts you can ignore actually, you were already appending the first ok.

You are appending an animation function later, which won't do anything and doesn't really make sense afaik. Just animate an element, no need to append it.

Ivan Horvatin

unread,
Oct 20, 2015, 3:37:38 AM10/20/15
to Snapsvg
I have corrected and tried some things, but i still cant get it to work.
I have this now:

<!DOCTYPE html>
    <head>
        <meta charset="utf-8">
        <title>Testing</title>
    </head>
    <body>
      <div><svg id="svgout"></svg></div>
    </body>
    <script src="Snap.svg-0.4.1/dist/snap.svg-min.js"></script>
    <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
     <script>
        var s = Snap("#svgout");
var svg1 = Snap.load("test1.svg", function(loadFragment1){
                                                s.append( loadFragment1 );
                                                s.hover( anim );
                                        });
var svg2 = Snap.load("test2.svg", function);

var anim = function() {svg1.animate ( { 'points':svg2.attr('points') }, 2000 )};

    </script>
</html>

And i still get unexpected token )  on the line with var svg2 = Snap.load("test2.svg", function);

But if i remove the function from svg2 and leave it like var svg2 = Snap.load("test2.svg"); . I get a tottaly different error, something like XMLHttpRequest cannot load .... Cross origin requests are only supported for protocol schemes...
And after that Uncaught TypeError: Cannot read property 'length' of undefined


I have a feeling im doing something terribly wrong here...

Ian

unread,
Oct 20, 2015, 4:21:34 AM10/20/15
to Snapsvg
var svg2 = Snap.load("test2.svg", function);

Snap.load takes a callback, it doesn't return anything, so no point in setting svg2 to its result, just leave that off.

'function' should be a function name, that does something. Eg it should be like the first example and call 'anim' or similar, rather than a javascript command like function.

Snap.load('test2.svg', someFunctionToHandleTheLoadedFragment);

function someFunctionToHandleTheLoadedFragment( f ) {
  s.append( f );
}

Something like that.

If you are getting cross site domain problems, you are probably trying to load the file externally from a different domain, and the browser or remote site isn't set to allow that. Not sure if you are trying it locally or on a remote server. You could try making sure both the script and the svg image file are on the same host.



Ivan Horvatin

unread,
Oct 20, 2015, 4:39:41 AM10/20/15
to Snapsvg
Im trying this out all localy, i have all files, including the svgs in the same folder. 
What if i dont want it to append. I wanted to load svg2 in the above example and just use its data as destination.
And out of all the data in both the svg's i wanted to use just the points that are in the <polygon> bracekts.

So does that mean i can still create the function in svg2, but instead of appending...i dunno..use selectAll? and somehow use that to animate the svg1?
Or use the animation inside the load svg2 function and do all the work in there?

Ian

unread,
Oct 20, 2015, 5:08:31 AM10/20/15
to Snapsvg
I'm guessing it gets more complex here, as you probably want to make sure BOTH files have loaded. Or you can reference it by clicking a button temporarily to start a function. If you do it the first way, you could set a flag firstFileLoaded and secondFileLoaded and do a check from both loads to see if both are set, if so, call your animation.

If you don't want to append it, you can use select/selectAll just to get the polygon values as you say.

Ivan Horvatin

unread,
Oct 20, 2015, 9:56:51 AM10/20/15
to Snapsvg
Ok, I managed to solve the problem with cross site domain. (yay me) Chrome wasn't very obedient, so I searched for a way around it.
Right now i get the first svg to show on screen, also when I click on it I don't get any errors. But the animation doesn't happen.
I thought making a var outside of a function would make it global, and I could use the points in the var anim = function() to make the animation to work. But nothing happens.

This is how it looks so far. 
I might be making mistakes again since i'm not sure how to load the other svg, without appending it and using its attributes.


<!DOCTYPE html>
    <head>
        <meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="../stilovi/main.css">
        <title>Testing</title>
    </head>
    <body>
      <div id="avengers"><svg viewBox="0 0 500 500" id="svgout"></svg></div>
    </body>
    <script src="snap.svg-min.js"></script>
    <script src="jquery-2.1.4.min.js"></script>
     <script>
        var s = Snap("#svgout");
var svg1 = Snap.load("test1.svg", function(loadFragment1){
                                                s.append( loadFragment1 );
                                                s.click( anim );
                                        });
Snap.load("test2.svg", animacija);
var svg2=null;
function animacija(f){
svg2 = f.selectAll("points");
}

var anim = function() {s.animate({'points':svg2.attr('points')},2000)};

    </script>
</html>


I will be checking if the whole thing loads later, i think this test examples are too small, if you think that would be the problem later on.

Ian

unread,
Oct 20, 2015, 10:49:42 AM10/20/15
to Snapsvg
What is 's.animate({ ..... supposed to be doing ?

s is the Snap paper/svg. So if you animate that, it won't have any points to animate. The animation needs to be on the element that has points.

Similar with f.selectAll('points'), there probably won't be anything there. You probably want the element name, like f.selectAll('polygon')

and then in the function something like...

var anim = function() {
  this.animate({ points: newPoints }, 2000);
}

You also can't do svg2.attr('points'),   only one element will have points, so you need to figure which element that is. 

You could do a forEach, pseudocode something like...

function anim() {
  var polys1 = svg1.selectAll('polygon');
  var polys2 = svg2.selectAll('polygon');
  polys1.forEach( function( el, index ) {
     polys1[index].animate({ point: polys2[index].point},2000);
     the above line probably won't interpolate a whole bunch of points in one go, so you will probably have to loop over each single point and animate that to its equivalent in polys2
  })
}

note, my guess this will be pretty ineffecient compared to css or svg markup, so I think this will be quite a bit of work, with disappointing results, but it may be an interesting experiment and learning curve.

Ivan Horvatin

unread,
Oct 21, 2015, 4:04:19 AM10/21/15
to Snapsvg
Hmmm...what would you purpose?
That i do it with SMIL instead? Because that generates a lot of code..im not sure how large are the usual svg animations used on the internet, but I don't know..almost 2 MB for just morphing one character into another seems much.
Especially if i will have more characters..

Maybe i could make the animation with SMIL with the help of javascript and make a loading animation until it finishes loading all the necessary parts?
SMIL's native <animate> does the interpolation by itself pretty nicely, i was just overwhelmed by the amount of code i generated by animating everything manualy.

Ian

unread,
Oct 21, 2015, 4:32:05 AM10/21/15
to Snapsvg
Not sure, you could take a look at css, but there will always be issues of size. That would be my first port of call if no interactivity with the svg parts itself is needed, and then see whats possible there (you could maybe take a look at velocity.js at some point with respect to performance).

Maybe before focusing too much on detail of accuracy using Snap to animate such a volume, you could just try animating them somewhere (eg just animate every point to the left by 200), doesn't matter where, just see if it grinds to a halt or looks smooth.


Ivan Horvatin

unread,
Oct 28, 2015, 6:11:39 AM10/28/15
to Snapsvg
Hey, its me again! :)

I have a question about something. I have this piece of code.

polygon.forEach(function(elem,i){
console.log("Iterated over a path element");
elem.animate({transform:'t-200,0'},3000,mina.easeinout);
}
);

This animates each element. Which is good. But.
Can i somehow get it to start with the animation when i click anywhere on the svg?

And where should i define that animation function then? Probably outside this for each polygon, but not sure how to stop the elem.animate for waiting on the click action.

Ian

unread,
Oct 28, 2015, 6:36:39 AM10/28/15
to Snapsvg
It depends a bit how many elements you have. If you just have a few elements, you can put the click handler on each element. If you have lots of elements, it may make sense to just put one handler on the svg or have them all in a group and put the click handler on the group.

If you just want to click anywhere to get it going, just put the handler on the paper/svg.

s.click(  loopThroughPolygonsFunction );

function loopThroughPolygonsFunction() {
   polygon.forEach blah blah
}

If polygons is a Snap set that you created earlier, you may be able to just animate the set...

//polygon is a set
polygons = s.selectAll('polygon')
polygons.animate({ transform.....

Ivan Horvatin

unread,
Oct 28, 2015, 8:29:44 AM10/28/15
to Snapsvg
I want the whole animation to start on click. But at the same time i want each element to render/animate individualy. Thats why im asking. Also there would be more animations each beggining from the last image that finished into another image.

I did try animating all the 650 polygons to one side by translating them and there is a performance drop..pretty drastic.. :/
So im thinking of how i can improve that. Any ideas?

Ian

unread,
Oct 28, 2015, 9:38:08 AM10/28/15
to Snapsvg
If you need to move everything the same amount, translate the group they are in.

If they are all different translations, you could try a css translation animation.
Reply all
Reply to author
Forward
0 new messages