Fire SVG animations (SMIL) when the SVG is visible
Had a use case come in where the design had an SVG animation that ran one interation and only one. It looked like this (some content has been removed):
If you’re on a device with a small vertical viewport size, you may not have noticed the above animation. Unfortunately the animations start (and complete!) whether or not the SVG is visible!
I wanted to wire the animations up to IntersectionObserver to make sure they only started animating when visible. Here’s how I did it:
Modify the SVG
I found all of the instances of <animate>
or <animateTransform>
and wired up the begin
attributes to properly cascade the order of the animation internally. I want all of them to start when my bezier curve starts animating, so I added an id
to that animation (there was an issue with dashes in that id
, so beware using dashes):
<animate
+ id="mysvgline"
attributeType="xml"
attributeName="stroke-dashoffset"
from="500"
to="0"
dur=".8s"
begin="0s"
/>
Learn how to animate a line/curve: CSS Tricks: How SVG Line Animation Works
Take note of the begin
attribute above, that will be important later.
Now I want to find the other animations in my SVG that I want to start at the same time and change their begin
attribute to use the id
from above with a .begin
suffix. This starts this animation when the referenced animation starts. It looks like this:
<animateMotion
+ begin="mysvgline.begin"
dur=".8s"
repeatCount="1"
fill="freeze"
path="M35.5 20C216.281 20 352.411 182 512.5 182"
/>
Alternatively, you can use .end
to start this animation when the referenced animation ends.
(Side note: repeatCount="1"
and fill="freeze"
are best buddies. fill="freeze"
means that your animation won’t rewind to the first frame at the end)
Next go back to the original animation and change the begin
attribute to indefinite
(Read more at MDN: begin
- SVG). This tells the SVG not to start it until I use JavaScript to trigger it using .beginElement()
.
<animate
id="mysvgline"
attributeType="xml"
attributeName="stroke-dashoffset"
from="500"
to="0"
dur=".8s"
+ begin="indefinite"
/>
(Another side note: just thinking aloud here as I write this—I wonder if I can use <noscript>
inside of SVG as a no-JS fallback)
Add the IntersectionObserver
It might look like this:
if ('IntersectionObserver' in window) {
// Recommended: make this selector more specific with a `data-animate-on-visible`
let elements = document.querySelectorAll("svg");
let observer = new IntersectionObserver(entries => {
// quit early if users wants Reduced Motion
let mediaQuery = window.matchMedia('(prefers-reduced-motion: no-preference)');
if(!mediaQuery.matches) {
return;
}
for(let entry of entries) {
if(!entry.isIntersecting) {
continue;
}
// Look for <animate> or <animateTransform> that need JS to start
let beginElements = entry.target.querySelectorAll(`:scope [begin="indefinite"]`);
for(let beginEl of beginElements) {
beginEl.beginElement();
// Unobserve so we don’t re-animate the dead
observer.unobserve(entry.target);
}
}
},
{
threshold: .5 // 50% of element must be visible
});
for(let elem of elements) {
observer.observe(elem);
}
}
15 Comments
@cport1
SMIL is a thing again?
@cport1
I guess with IE becoming chromium it's relevant again.. which is awesome <3 I remember asking this on stackoverflow years ago... stackoverflow.com/questions/3192…
@zachleat
oh yeah it never left—look at this beautiful green
@cport1
I really loved SMIL back in the day, but dropped it for a few projects because of this limitation in some enterprise projects. Definitely opens the doors for some new considerations.
@zachleat
Honestly I only recently experimented with it but it’s working great!
@Bliepjes
Forgive me for asking the obvious maybe, but what's the support for SMIL as I read it in Drasner's book SVG animations, but don't see anyone using it or in any animation tooling as export. I love SVG, no doubt, but I do want it to last.
@zachleat
strange that the Edge switch to Chromium may have saved it? caniuse.com/svg-smil Note the warning on developer.mozilla.org/en-US/docs/Web…
@ladyofcode
I didn't bother with it much expecting it to disappear, but I guess now is a good time to dive right in?
@Bliepjes
In my perception they are the same to SVG's animation attributes. If it's just a difference in name, it's already native I guess? Something we just don't think about anymore? Because otherwise, what is driving SVG animations natively? 😅
@Bliepjes
One thing off the topic of SMIL, is that in SVG it's really sad you can't influence the scale of a displacementmap in the arguments like: <feDisplacementMap> ... scale="var(--scale)" ... so you can animate it in CSS. They need hardcoded values. That would … Truncated
@TheGreenGreek
"save it in my blog brain" is one of the top reasons I write blog posts 😆 I'm always referencing my own posts to remember how to do something
@jkup
I love your open graph images so much
@zachleat
why thank you Jhon I wrote a blog post recently on this very topic
@jkup
I READ IT!!
@zachleat
well that was your first mistake