SVG Plugin

The SVG Plugin for KUTE.js extends the core engine and enables animation for various SVG specific CSS properties, SVG morphing of path shapes and SVG transforms. We'll dig into this in great detail as well as provide valuable tips on how to configure your animation for best performance and visual aesthetics. The SVG Plugin is very light, maybe one of the lightest out there, still, you will find it to be very powerful and flexible.

Keep in mind that older browsers like Internet Explorer 8 and below as well as stock browser from Android 4.3 and below do not support inline SVG so make sure to fiter out your SVG tweens.

SVG Morphing

One of the most important parts of the plugin is the SVG morphing capability. It only applies to inline <path> and <glyph> SVG elements, with closed shapes (their d attribute ends with z). 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 with these points, the arrays that we need for interpolation. Further more, with a set of options we can then rearrange / reverse these arrays to optimize and / or maximize the visual effect of the morph:

Basic Example

In the first morph example we are going to go through some basic steps on how to setup and how to improve the morph animation. Our demo is a morph from a rectangle into a star, so first let's create an SVG element with two paths, first is going to be visible, filled with color, while second is going to be hidden. The first path is the start shape and the second is the end shape, you guessed it, and 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" class="bg-lime" 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:

As you can see, the animation could need some fine tunning. Let's go ahead and play with the new utility, it's gonna make your SVG morph work a breeze.

Well, we're going to set the morphIndex: 127 tween option and we will get an improved morph. Sometimes the recommended value isn't what we're looking for, so you just have to experience values around the recommended one. I also made a pen for you to play with.

Much better! You can play with the morphIndex value, maybe you can get a more interesting morph.

Morphing Polygon Paths

When your paths are only lineto, vertical-lineto and horizontal-lineto based shapes (the d attribute consists of L, V and H path commands), the SVG Plugin will work differently: it will use their points instead of sampling new ones. As a result, we boost the visual and maximize the performance. The morphPrecision option will not apply since the paths are already polygons, still you will have access to all the other options.

The plugin will try to convert paths to absolute values for polygons, but it might not find most accurate coordinates values for relative v and h path commands. I highly recommend using my utility converter to prepare your paths in that case.

// 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();

In the example below the triangle shape will morph into a square, then the square will morph into a star, so 2 tweens chained with a third that will morph back to the original triangle shape. For each tween the morph will use the number of points from the shape with most points as a sample size for the other shape. Let's have a look at the demo.

The morph for polygon paths is the best morph in terms of performance so it's worth keeping that in mind. Also using paths with only L path command will make sure to prevent value processing and allow the animation to start as fast as possible.

Multi Path 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 600 600">
<path 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
    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
    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
    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 d="M0.175 256l-0.175-156.037 192-26.072v182.109z
    M224 69.241l255.936-37.241v224h-255.936z
    M479.999 288l-0.063 224-255.936-36.008v-187.992z
    M192 471.918l-191.844-26.297-0.010-157.621h191.854z"/>
</svg>

As you can see, both these paths have subpaths, and KUTE.js will only animate the first of both in this case. To animate them all, we need to break them into multiple paths, so we can handle each path morph properly.

<svg id="multi-morph-example" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600">
    <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="M0.175 256l-0.175-156.037 192-26.072v182.109z"/>
    <path id="w22" style="visibility:hidden" d="M224 69.241l255.936-37.241v224h-255.936z"/>
    <path id="w23" style="visibility:hidden" d="M479.999 288l-0.063 224-255.936-36.008v-187.992z"/>
    <path id="w24" style="visibility:hidden" d="M192 471.918l-191.844-26.297-0.010-157.621h191.854z"/>
</svg>

After a close inspection we determined that paths are not ordered the same so it seems we need to tween the paths in a way that their points travel the least possible distance, as follows: #w11 to #w24, #w13 to #w21, #w14 to #w22 and #w12 to #w23.

Now we can write the tween objects and get to working:

var multiMorph1 = KUTE.to('#w11', { path: '#w24' }).start();
var multiMorph2 = KUTE.to('#w13', { path: '#w21' }).start();
var multiMorph3 = KUTE.to('#w14', { path: '#w22' }).start();
var multiMorph3 = KUTE.to('#w12', { path: '#w23' }).start();

As you can imagine, it's quite hard if not impossible to code something that would do all this work automatically, so after a minute or two tweaking the options, here's what we should see:

Note that this final touch required using reverseSecondPath:true option for all tweens because each shape have a slightly different position from its corresponding shape, so make sure to check the svg.js for a full code review.

Complex Example

The last morph example is a bit more complex as the paths have subpaths with different positions and other important differences such as having different amounts of subpaths as well as significant differences of their positions. In this case you have to manually clone one or more paths 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 end shapes; at this point you should be just like in the previous example.

An important aspect of multi path morph is syncronization: since the .to() method will prepare the paths for interpolation at animation start, and this usually takes a bit of time, the problem can be easily solved as always using the .fromTo() method. So, let's get into it:

// complex multi morph, the paths should be self explanatory
var morph1 = KUTE.fromTo('#start-container',  { path: '#start-container' },    { path: '#end-container' });
var morph2 = KUTE.fromTo('#startpath1',       { path: '#startpath1' },         { path: '#endpath1' });
var morph3 = KUTE.fromTo('#startpath1-clone', { path: '#startpath1-clone' },   { path: '#endpath1' });
var morph4 = KUTE.fromTo('#startpath2',       { path: '#startpath2' },         { path: '#endpath2' });

As with the previous example, you should change which path will morph to which path so that their points travel the least possible distance and the morph animation looks visually appealing. In the next example, we have used a mask where we included the subpaths of both start and end shape, just to get the same visual as the originals.

So you have many options to improve the visual and performance for your complex animation ideas. The SVG Plugin for KUTE.js uses approximatelly the same algorithm as D3.js for determining the coordinates for tween, it's super light, it's a lighter script, it might be a better solution for your applications.

Recommendations

Drawing Stroke

Next, we're going to animate the stroking of some elements. Starting with KUTE.js version 1.5.2, along with <path> shapes, <circle>, <ellipse>, <rect>, <line>, <polyline> and <polygon> shapes are also supported; the script uses the SVG standard .getTotalLength() method for <path> shapes, while the others use some helper methods. Here some code examples:

// draw the stroke from 0-10% to 90-100%
var tween1 = KUTE.fromTo('selector1',{draw:'0% 10%'}, {draw:'90% 100%'});

// draw the stroke from zero to full path length
var tween2 = KUTE.fromTo('selector1',{draw:'0% 0%'}, {draw:'0% 100%'});

// draw the stroke from full length to 50%
var tween3 = KUTE.fromTo('selector1',{draw:'0% 100%'}, {draw:'50% 50%'});

We're gonna chain these tweens and start the animation real quick.

Remember: the draw property also accepts absolute values, eg. draw: '0 150'; the .to() method takes 0% 100% as start value for your tweens when stroke-dasharray and stroke-dashoffset are not set.

SVG Transforms

Starting with KUTE.js 1.5.2, the SVG Plugin features a new tween property for cross browser SVG transforms, but was coded as a separate set of methods for SVG only, to keep performance tight and solve most browser inconsistencies. A very simple roadmap was described here; in brief we needed to find a way to enable SVG transforms in a reliable and cross-browser supported fashion.

With KUTE.js 1.6.0 the SVG transform is a bigger part of the SVG Plugin for two reasons: first is the ability to use the transformOrigin just like for CSS3 transforms and secondly the unique way to normalize translation to work with the transform origin in a way that the animation is just as consistent as for CSS3 transforms on non-SVG elements. Also the value processing is consistent with the working draft.

While you can still use regular CSS3 transforms for SVGs on browsers like Google Chrome, Opera and others, Firefox struggles big time with the percentage based transform-origin values and ALL Internet Explorer versions have no implementation for CSS3 transforms on SVG elements.

KUTE.js SVG Plugin comes with a better way to animate transforms on SVGs shapes reliably on all browsers, by the use of the transform presentation attribute and the svgTransform tween property with a special notation:

// using the svgTransform property works in all SVG enabled browsers
var tween2 = KUTE.to('shape', {svgTransform: { translate: [150,100], rotate: 45, skewX: 15, skewY: 20, scale: 1.5 }});

// regular CSS3 transforms apply to SVG elements but not all browsers fully/partially supported
var tween1 = KUTE.to('shape', { translate: [150,100], rotate: 45, skewX: 15, skewY: 20, scale: 1.5 }, { transformOrigin: '50% 50%' });

As you can see we have some familiar notation, but an important notice here is that svgTransform tween property treat all SVG transform functions as if you are using the 50% 50% of the shape box at all times by default, even if the default value is "0px 0px 0px" on SVGs in most browsers.

Perhaps the most important thing to remember is the fact that SVG tranformations always use SVG coordinates system, and the transform attribute accepts no measurement units such as degrees or pixels. For these reasons the transformOrigin tween option can also accept array values just in case you need coordinates relative to the parent <svg> element. Also values like top left values will work.

In the following examples we showcase the animation of CSS3 transform applied to SVG shapes (LEFT) as well as svgTransform based animations (RIGHT). I highly encourage you to test all of them in all browsers, and as a word ahead, animations in Webkit browsers will look identical, while others are inconsistent or not responding to DOM changes. Let's break it down to pieces.

SVG Rotation

Our first chapter of the SVG transform is all about rotations, perhaps the most important part here. As of with KUTE.js 1.6.0 the svgTransform will only accept single value for the angle value rotate: 45, the rotation will go around the shape's center point by default, again, contrary to the browsers' default value and you can set a transformOrigin tween option to override the behavior.

The argument for this implementation is that this is something you would expect from regular HTML elements rotation and probably most needed, not to mention the amount of savings in the codebase department. Let's have a look at a quick demo:

The first tween uses the CSS3 transform notation and the animation clearly shows the shape rotating around it's center coordinate, as we've set transformOrigin option to 50% 50%, but this animation doesn't work in IE browsers, while in Firefox is inconsistent with the SVG coordinate system. The second tween uses the rotate: 360 notation and the animation shows the shape rotating around it's own central point and without any option, an animation that DO WORK in all SVG enabled browsers.

When for CSS3 transforms we could have used values such as center bottom as transform-origin (also not supported in all modern browsers for SVGs), the entire processing was basically in/by the browser, however when it comes to SVGs the plugin here will compute the transformOrigin tween setting value accordingly to use a shape's .getBBox() value to determine for instance the coordinates for 25% 75% position or center top.

In other cases you may want to rotate shapes around the center point of the parent <svg> or <g> element, and we use it's .getBBox() to determine the 50% 50% coordinate, so here's how to deal with it:

// rotate around parent svg's "50% 50%" coordinate as transform-origin
// get the bounding box of the parent element
var svgBB = element.ownerSVGElement.getBBox(); // returns an object of the parent <svg> element

// we need to know the current translate position of the element [x,y]
// in our case is:
var translation = [580,0];

// determine the X point of transform-origin for 50%
var svgOriginX = svgBB.width * 50 / 100 - translation[0];

// determine the Y point of transform-origin for 50%
var svgOriginY = svgBB.height * 50 / 100 - translation[1];

// set your rotation tween with "50% 50%" transform-origin of the parent <svg> element
var rotationTween = KUTE.to(element, {svgTransform: {rotate: 150}}, { transformOrigin: [svgOriginX, svgOriginY]} );

Note that this is the only SVG transform example in which we have adapted the transform-origin for the CSS3 transform rotation so that both animations look consistent in all browsers, and if you are interested in learning about this fix, similar to the above, just we are adding "px" to the calculated value, but you better make sure to check svg.js file.

SVG Translation

In this example we'll have a look at translations, so when setting translate: [150,0], the first value is X (horizontal) coordinate to which the shape will translate to and the second value is Y (vertical) coordinate for translation. When translate: 150 notation is used, the script will understand that's the X value and the Y value is 0 just like for the regular HTML elements transformation. Let's have a look at a quick demo:

The first tween uses the CSS3 translate: 580 notation for the end value, while the second tween uses the translate: [0,0] as svgTransform value. For the second example the values are unitless and are relative to the viewBox attribute.

SVG Skew

For skews for SVGs we have a very simple notation: skewX: 25 or skewY: -25 as SVGs don't support the skew: [X,Y] function. Here's a quick demo:

The first tween skews the shape on both X and Y axis in a chain via regular CSS3 transforms and the second tween skews the shape on X and Y axis via the svgTransform tween property. You will notice translation kicking in to set the transform origin and the example also showcases the fact that chain transformations for SVGs via transform attribute works just as for the CSS3 transformations.

SVG Scaling

Another transform example for SVGs is the scale. Unlike translations, for scale animation the plugin only accepts single value like scale: 1.5, for both X (horizontal) axis and Y (vertical) axis, to keep it simple and even if SVGs do support scale(X,Y). But because the scaling on SVGs depends very much on the shape's position, the script will always try to adjust the translation to make the animation look as we would expect. A quick demo:

The first tween scales the shape at scale: 1.5 via regular CSS3 transforms, and the second tween scales down the shape at scale: 0.5 value via svgTransform. If you inspect the elements, you will notice for the second shape translation is involved, and this is to keep transform-origin at an expected 50% 50% value. A similar case as with the skews.

SVG Mixed Transform Functions

Our last transform example for SVGs is the mixed transformation. Just like for the other examples the plugin will try to adjust the rotation transform-origin to make it look as you would expect it from regular HTML elements. Let's combine 3 functions at the same time and see what happens:

Both shapes are scaled at scale: 1.5, translated to translate: 250 and skewed at skewX: -15. If you inspect the elements, you will notice the second shape's translation is different from what we've set in the tween object, and this is to keep transform-origin at an expected 50% 50% value. This means that the plugin will also compensate rotation transform origin when skews are used, so that both CSS3 transform property and SVG transform attribute have an identical animation.

Chained SVG transforms

The SVG Plugin does not work with SVG specific chained transform functions right away (do not confuse with tween chain), but if your SVGs only use this feature to set a custom transform-origin, it should look like this:

<svg>
    <circle transform="translate(150,150) rotate(45) scale(1.2) translate(-150,-150)" r="20"></circle>
</svg>

Well in this case I would recommend using the values of the first translation as transform-origin for your tween built with the .fromTo() method like so:

// a possible workaround for animating a SVG element that uses chained transform functions
KUTE.fromTo(element,
    {svgTransform : { translate: 0, rotate: 45, scale: 0.5 }}, // we asume the current translation is zero on both X & Y axis
    {svgTransform : { translate: 450, rotate: 0, scale: 1.5 }}, // we will translate the X to a 450 value and scale to 1.5
    {transformOrigin: [256,256]} // tween options use the transform-origin of the target SVG element
).start();

Before you hit the Start button, make sure to check the transform attribute value. The below tween will reset the element's transform attribute to original value when the animation is complete.

This way we make sure to count the real current transform-origin and produce a consistent animation with the SVG coordinate system, just as the above example showcases.

Recommendations for SVG Transforms

SVG Plugin Tips