SMIL, SVG’s native animation specification, has been highly regarded because it offers so many bells and whistles for performant SVG animation rendering. Unfortunately, support for SMIL is waning in WebKit, and has never (nor will likely ever) exist for Microsoft’s IE or Edge browsers. Have no fear! We’ve got you covered. This article explores some of those SMIL-specific features and delves into the alternatives to achieve the same effects with a longer tail of support.
SMIL Feature: Motion Along a Path
One of the most compelling things that SMIL offers for realistic motion in SVG is motion along a path. Very few things in real life move along a straight line, so motion along a path allows us to mimic what we see in everyday life. Previously, you would pass an SVG path into animateMotion, with path and define the path data. You pick the element to animate by designating it with xlink:href=”#thingtoanimate”.
<animateMotion
xlink:href="#lil-guy"
dur="3s"
repeatCount="indefinite"
fill="freeze"
path="M 25,50 C 37.5,25 37.5,25 50,0 75,50 75,50 100,100 50,100 50,100 0,100 12.5,75 12.5,75 25,50 Z" />
See the Pen SMIL motion path by Sarah Drasner (@sdras) on CodePen.
An Alternative: CSS
Luckily for us, the motion along a path module is now moving into CSS. The support thus far is slim (Chrome, Opera, and Android only):, but Sara Soueidan has proposed adoption in Edge, and it has strong support thus far, with around 420+ votes at the time of the article’s publication. Please add your voice to ensure that this feature will be shipped. The voting for Firefox is here.
Voting for support in Safari, as far as I can gather, is a little more individual. I registered to fill out a bug report and requested motion path module in CSS as a feature.
In order to use motion along a path in CSS, you pass the path data into the offset-path property like so:
.move-me {
offset-path: path('M3.9,74.8c0,0,0-106.4,75.5-42.6S271.8,184,252.9,106.9s-47.4-130.9-58.2-92s59.8,111.2-32.9,126.1 S5.9,138.6,3.9,74.8z');
}
See the Pen Playing with Motion Path Module in CSS by CSS-Tricks (@css-tricks) on CodePen.
I get path data from an SVG I make in Illustrator and then optimize in SVGOMG.
In this instance, I want to ensure that it follows the path from the starting point all the way to the end of the path, and you can see that it is a closed path by the small z at the end. This means this path is in a loop, so the weird little creature will end up at the same place he started from. I set these parameters in my keyframes values, only specifying the 100% value because the default is set at zero:
@keyframes motionpathguy {
100% {
motion-offset: 100%;
}
}
and then call the animation on the element:
.move-me {
animation: motionpathguy 10s linear infinite both;
}
An Alternative: GreenSock’s Motion Along a Path
If you want the widest current support and the most flexible implementation, you should be using GreenSock. GSAP’s Bezier-Plugin (packaged in by default with TweenMax) offers support back to IE7 for non-SVG elements, and back to IE9 for SVG (the broadest SVG animation support available). It works like gangbusters on mobile. I’ve written about this before on the David Walsh Blog, but here’s a small recap, plus a few extra new features that have come out since then:
Originally you would pass in an array of values:
bezier: {
type: "soft",
values:[{x:10, y:30}, {x:-30, y:20}, {x:-40, y:10}, {x:30, y:20}, {x:10, y:30}],
autoRotate: true
}
But as you can see here, you can also have the option of autorotating (or not) just like SMIL’s rotate. If you’re missing SMIL’s ability to specify auto-reverse or auto:n
parameter for initial position of rotation or degrees of rotation, GSAP allows you to use rotation:90 to change the number of degrees, or more finite control should you need it:
autorotate: [
first position property, like "x",
second position property, like "y",
rotation property, typically "rotation" but can be “rotationY”,
integer for radians/degrees the rotation starts from like 10,
boolean for radians or degrees- radians is true
]
In SMIL, you can transform the path or group to change that orientation on the object while it’s moving. In GSAP, you can achieve this easily with autoRotate: false
, and initializing the rotation with set. You can also transform the element on the SVG attribute itself like you would with SMIL, although that’s a little less elegant and harder to keep track of while you’re working.
TweenMax.set("#foo" {
rotation: 90 // or whatever number
});
You can also set the type to thru
, soft
, quadratic
, or cubic
. More documentation on each of these are in the GreenSock API docs. A nice value of thru
is being able to affect the amount of curviness on an element. If you think about the points as coordinates to bounce to and from, the curviness would control how direct a path to take between those points. 0 would be a direct path, 1 is a little looser, with 2 making a nice curve, and values of 3 and higher begin winding on itself.
See the Pen Demo for Curviness in GreenSock Bezier by Sarah Drasner (@sdras) on CodePen.
More recently, GreenSock has also exposed the ability to pass in path data like the CSS and SMIL modules, as you would with native SMIL. This comes as an extension to their MorphSVG plugin, so you would add the plugin, and use it like this:
TweenMax.to("#lil-guy", 3, {
bezier: {
MorphSVGPlugin.pathDataToBezier("#path", {align: "#lil-guy" }),
type: "cubic"
},
ease: Linear.easeNone,
repeat: -1
});
<path id="path" d="M 25,50 C 37.5,25 37.5,25 50,0 75,50 75,50 100,100 50,100 50,100 0,100 12.5,75 12.5,75 25,50 Z" fill="none" />
See the Pen SMIL motion path by Sarah Drasner (@sdras) on CodePen.
The default would have set the upper left corner of the group I’m animating, in this case, #lil-guy
, to the path trajectory. This would have made it visually look misaligned. So I set #lil-guy
to use the centerpoint instead using TweenLite.set
:
TweenLite.set("#lil-guy", {xPercent:-50, yPercent:-50});
You can also offset these paths by passing an object as the second parameter to that method and define an offsetX
and offsetY
within pathDataToBezier
– beware that you might need to expand the viewBox
so that the group or attribute you’re animating doesn’t get cropped out. Formatting nerds: I’m placing the objects on new lines for legibility reasons.
// offset the path coordinates by 125px on the x-axis, and 50px on the y-axis:
TweenMax.to("#lil-guy", 3, {
bezier: {
values: MorphSVGPlugin.pathDataToBezier("#path", {
offsetX: 125,
offsetY: 50,
align: "#lil-guy"
}),
type: "cubic"
},
ease: Linear.easeNone,
repeat: -1
});
See the Pen SMIL motion path by Sarah Drasner (@sdras) on CodePen.
You can even define a matrix coordinate for this positioning.
// scale the path coordinates up by 1.25
// and shift it over 120px on the x-axis
// and up 30px on the y-axis:
TweenMax.to("#lil-guy", 3, {
bezier: {
values: MorphSVGPlugin.pathDataToBezier("#path", {
matrix:[1.5,0,0,1.5,120,-30],
align:"lil-guy"}),
type: "cubic"
},
ease: Linear.easeNone,
repeat: -1
});
See the Pen SMIL motion path by Sarah Drasner (@sdras) on CodePen.
Another option is to set the align
member to "relative"
. This keeps it from jumping by keeping the position of each coordinate relative to x:0
, y:0
. In the previous pens, I used align
to pair the movement with the #lil-guy
group itself.
For more information on this new (as in new the day of this post!) feature in GreenSocks’s Bezier Plugin API, check out their docs, and this great explanatory video.
SMIL Feature: Shape Morphing
Previously, you would be able to pass path data as values into animate attribute in order to morph a shape. Noah Blon has a great example:
See the Pen Sitepoint Challenge #1 in SVG and SMIL by Noah Blon (@noahblon) on CodePen.
An Alternative: Snap.svg or SVG Morpheus
Some libraries offer morphing path or shape values, such as Snap.svg and SVG Morpheus, but the caveat (even in SMIL) is that the shape has to have the same number of points or the morphing looks terrible or fails entirely. This is disappointing on the pre-processing end, because it means that you have to keep good track of what you’re making, or collaborate well with your designer to make sure you’re being fed this (sometimes arbitrary) midpoint data. The extra points also unnecessarily bloat your code.
An Alternative: GreenSock MorphSVG
I would highly recommend GSAP’s MorphSVG plugin, as it beautifully morphs shapes and paths with different amounts of points. See the toggle on the logo of this very website for a demo of the morph in action. Here’s another example:
See the Pen Interchangable Hipster by Sarah Drasner (@sdras) on CodePen.
Because MorphSVG plugin tweens path data, if you need to convert a shape, you would use their convertToPath
option:
MorphSVGPlugin.convertToPath("ellipse");
// or circle, rect, etc
This allows us to do really complex shape tweening, and is a game-changer for all of motion on the web.
There are a few extra features that this plugin offers that really knocks it out of the park. The first is the utility plugin findShapeIndex
. Let’s say you are unhappy with the way that the shape is morphing (though 9 times out 10, the auto preset will work just fine). You load up the plugin (don’t worry, you won’t add extra weight because it’s not needed in production), and you pass in two values: the ID of the first shape to tween and the ID of the second. A GUI will pop up where you can toggle between values, and it will also automatically use a repeat: -1
, so that it will continually loop between the shapes.
findShapeIndex("#hex", "#star");
// you can comment out above line to automatically disable findShapeIndex() UI
See the Pen SMIL motion path by Sarah Drasner (@sdras) on CodePen.
Once you have that additional value, you would pass the shapeIndex
within the morphSVG
object:
TweenLite.to("#hex", 1, {morphSVG: { shape: "#star", shapeIndex: 1 }});
The second of these extra features is the plugin’s ability to parse cut-out-paths, which no other library offers. Finally, you can also reuse the first starting ID (rather than having to store that path data for reuse). It’s worth mentioning that when the plugin was first released, these features were not available, but GreenSock recognized the need for support, and so, included it.
Now that we aren’t tied down to a specified number of points, we’ve widened the possibility for different kinds of effects. Below, I made some smoke:
See the Pen Where There’s Smoke by Sarah Drasner (@sdras) on CodePen.
SMIL Feature: DOM events
Things like hover, and click were nicely baked into SMIL. In order to kick things off, it was possible to specify begin="click"
or begin="hover"
.
<animate
xlink:href="#rectblue"
attributeName="x"
from="0"
to="300"
dur="1s"
begin="click"
values="20; 50"
keyTimes="0; 1"
fill="freeze" />
See the Pen SMIL motion path by Sarah Drasner (@sdras) on CodePen.
An Alternative: JavaScript
There are the native DOM events like onmouseenter
and onmouseleave
for hover and click
for, ya know, clicks. You could use them to change to trigger your JavaScript-based animations.
An Alternative: JavaScript + CSS
You could use JavaScript to change a class name or directly change CSS styles. Here’s on possibility: change the animation-play-state
to start the animation from an event trigger.
.st0 {
animation: moveAcross 1s linear both;
animation-play-state: paused;
}
@keyframes moveAcross {
to {
transform: translateX(100px);
}
}
document.getElementById("rectblue").addEventListener("click", function() {
event.target.style.animationPlayState = "running";
});
or in jQuery:
$(".st0").on("click", function() {
$(this).css("animation-play-state", "running");
});
See the Pen SMIL motion path by Sarah Drasner (@sdras) on CodePen.
This implementation would not immediately set this animation back to the beginning again as in the SMIL example. If you wanted to accomplish that, a previous article on CSS-Tricks details a few good ways of doing so.
An Alternative: Greensock
In GSAP, the restart is more simple. We could add the animations to a timeline, set it to paused, and then on click restart it. This implementation is a little closer to what you’d expect of SMIL, because we don’t have to do anything hacky like clone/reinsert a DOM node or change any properties set on the element.
// instantiate a TimelineLite
var tl = new TimelineLite();
// add a tween to the timeline
tl.to(foo, 0.5, { left: 100 });
$(".st0").on("click", function() {
tl.restart();
});
SMIL Feature: Run “X” after “Y” is completed
SMIL also allowed for more complicated timed events such as begin="circ-anim.begin + 1s"
. This is particularly useful when chaining animations.
An Alternative: CSS
In CSS, we could chain the animations by setting a delay on the second value:
.foo {
animation: foo-move 2s ease both;
}
.bar {
animation: bar-move 4s 2s ease both;
/* the 2 second value corresponds with the length of the iteration of the first. */
}
This way of working a bit of a bummer because you have to make sure you remember to change the first interval as well as the delay.
An Alternative: CSS Preprocessing
Maintaining and managing those intervals can get a little easier if we use a variable in (for example) Sass:
$secs: 2s;
.foo {
animation: foo-move $secs ease both;
}
.bar {
animation: bar-move 4s $secs ease both;
}
Now we know that if we update the one value, they’ll stay in sync.
But if we want to always detect when the animation is complete, JavaScript offers some nice native functionality for this with animationEnd
:
$("#rectblue").on("animationend", function() {
$(this).closest("svg").find("#rectblue2").css("animation-play-state", "running");
});
#rectblue2 {
animation: moveAcross 2s 1s ease both;
animation-play-state: paused;
}
You may need to hit replay to see the animation below:
See the Pen SMIL motion path by Sarah Drasner (@sdras) on CodePen.
For the delay, we would bake it into the animation-delay
property of CSS of the element itself, like above, or we could express this with a setTimeout
:
setTimeout(function timeoutHandler() {
// animation goes here, with any language
}, 1000); // wait for a second
An Alternative: Greensock
My favorite option is to add finite control of the animation timeline. We can use GreenSock’s TimelineLite for this, which can be expressed a few different ways:
Simple Timeline:
// instantiate a TimelineLite
var tl = new TimelineLite();
// add a tween at the beginning of the timeline
tl.to(foo, 0.5, { left: 100 });
// use the position parameter "+=1" to schedule next tween 1 second after the previous tweens end
tl.to(foo, 0.5, { left: 200 }, "+=1");
Timeline with a relative label:
// add a label 0.5 seconds later to mark the placement of the next tween
tl.add("myRelativeLabel")
// use the label to specify an animation a second after the
tl.to(foo, 0.5, { scale: 0 }, "myRelativeLabel+=1");
// or to use to this label for things like interaction
tl.play("myRelativeLabel");
I prefer relative labels, because you can choose a point in time that a lot of things fire, or are delayed by, and even if that point in time adjusts, you don’t have to do any recalculation as you would have to in CSS.
The nice thing about the timeline is that you then have fine control over many different objects in just one place, and can offer things like repeatDelay
(delays in between multiple repeats).
SMIL offers repeatDur
, which lets you say how long the repeat iteration would be, if you don’t want it to be the default such as repeatDur="01:30"
. In GreenSock, you can speed up or slow down the timeline, thereby adjusting the repeat length with timeScale(n)
, or set a repeat: -1
for times that you would otherwise set repeatDur="indefinite"
.
Handy Dandy Replacement Reference Chart
Now that we’ve done a deep-dive into some of the most useful SMIL-specific features, it’s worth noting that here are many more replacements for SMIL functionality that you might already know about. We’ve made the small table below for quick reference of these simpler implementations.
SMIL Code | Replacement Code | Replacement Technology |
keyTimes | @keyframes | CSS |
keySplines | cubic-bezier | CSS |
restart | restart(); | GSAP |
calcMode=”discrete” | steps() | CSS |
remove |
kill(); clear(); clearProps: “all” |
GSAP |
freeze | animation-play-state: paused | CSS |
freeze | pause(); | GSAP |
fill | animation-fill-mode | CSS |
repeatCount=”indefinite” | animation-iteration-count: infinite; | CSS |
repeatCount=”indefinite” | repeat: -1 | GSAP |
whenNotActive | detect animation-play-state in JS | CSS, vanilla JavaScript or jQuery |
animateMotion path | motion-path | CSS |
animateMotion path | bezier | GSAP |
animate values (path morphing) | MorphSVG | GSAP |
begin=”hover” |
mouseover, mouseenter/ mouseout, mouseleave |
jQuery, vanilla JS |
begin=”click” | click | jQuery, vanilla JS |
begin=”circ-anim.begin + 1s” | animation-delay: $vars; | SASS |
begin=”circ-anim.begin + 1s” | timeline, position parameter ex “+=1” | GSAP |
Don’t want javascript or complications, just want a simple scale from 0 to 1, duration 10s, default x and y coordinates.
If you visit http://svgdesign.guru you will see that the turtle flies into the screen using this simple scale. What can be better then SMIL? I can’t find an alternative
That same effect can be achieved using simple CSS Transforms (scale and translate) inside a Keyframe animation.
I recently did an experiment pen& post with a vanilla js version of a shape morph,
Post: http://codepen.io/vidhill/post/svg-morph-without-smil
Pen: http://codepen.io/vidhill/pen/MKWRjo
Based on my musing on what would one do to recreate it without the necessity to include an extra library (if you weren’t already using one )
It is subject to the same number of points limitation
Steven; Show me your CSS way and then explain how that is a better way then using one line of declarative SMIL.
Too much cringe in that “website”, especially the diatribe about chrome.
Use flexbox, viewport-size, things like this instead of position absolute.
For your turtle some help :
You really do a simple animation with that turtle.
Yimi;
Thanks for informing me that flexbox allows for scalable HTML text and pixel perfect positioning of CSS div’s within a document structure! I didn’t know that.
Also did not know that a scale is considered an animation.
Hi David Hill,
I really like your SVG Morph example. Super cool, I’ve bookmarked it. One thing I do want to stress slightly is that your example, though awesome, has a pretty easy morph. The bells and whistles GSAP offers falls along the line of more complex path shape morphs with uneven points (see the smoke demo again for what I’m referring to), and with very simplified code. The ability to toggle between different anchor points to finely control the morph is great, too, particularly for when you’re in a time crunch and don’t want to rewrite a ton of JS to get a slightly different effect. Great demo, though, and nice post as well.
Thanks Sarah, I really needed an article like this. After running against issues implementing a cross-compatible set of SMIL techniques for more than just webkit browsers I was unsure which route to take (GreenSock versus JS polyfills).
Kudos to you and Sara Soueidan as well.
One question – GreenSock typically charges license fees for using some of it’s more advanced plugins, like Morph. Any sign of that changing with more adoption or public petitioning?
Jack Doyle and Carl Schoof are the ones who maintain Greensock. They do not have other day jobs. Greensock is all they do. They pour their heart and soul into that library. I think the amount of power you get for the free version is very generous. The advanced plug-ins is their only source of income to keep them maintaining the library. You are basically asking them to work for free. Do you work for free?
Thanks Paul Kane! Also, I agree with Elliot Geno- you definitely get what you pay for and the GreenSock team is well worth the money.
I will keep this open until I work on the migration from SMIL to anything else of my game esviji … thanks!
You’re welcome! Glad it was useful.
Great post Sarah, luckily I’ve never had to use SMIL. I’ve been a long time GSAP user mainly because of the great browser support and easy workflow.
Great article Sarah, there’s so much alternatives.
Great article as always Sarah. My brief date with SMIL was awkward and involved me staring across the restaurant table at it waiting for it to say something amazing. It didn’t. My soup got cold. I went home, fired up GSAP and had a Pot Noodle.
Haha, yeah, I worked with SMIL mostly before I even touched GSAP. That’s part of why I’ve been so pleasantly surprised with this library.