Zach’s ugly mug (his face) Zach Leat­herman

Fire SVG animations (SMIL) when the SVG is visible

5k 00 August 04, 2021

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’s AvatarCSS 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);
  }
}

Try it yourself


< Older
If I work really hard on my Open Graph Images, People will share my Blog Posts
Newer >
Vector? Raster? Why Not Both!

Zach Leatherman IndieWeb Avatar for https://zachleat.com/is a builder for the web at Font Awesome and the creator of Build Awesome (née IndieWeb Avatar for https://www.11ty.devEleventy/11ty), an award-winning open source website generator. He measures website performance with speedlify and at one point became too fixated on web fonts. He has given 89 talks in nine different countries at events like Beyond Tellerrand, Smashing Conference, Jamstack Conf, CSSConf, and The White House. Formerly part of CloudCannon, Netlify, Filament Group, NEJS CONF, and NebraskaJS. Learn more about Zach »

Shamelessly plug your related post

These are webmentions via the IndieWeb and webmention.io.

Sharing on social media?

This is what will show up when you share this post on Social Media:

How did you do this? I automated my Open Graph images. (Peer behind the curtain at the test page)