Back to Learn SVG Animation

Scroll-Triggered SVG Animation: Where to Start

By Published Updated education

Start with IntersectionObserver when scroll should only start an SVG animation; use CSS animation-timeline when progress must follow the viewport; choose GSAP ScrollTrigger for scrubbed, pinned, multi-step scenes. Ship a visible baseline and disable non-essential motion.

Choose the scroll behavior before you choose the tool

The first decision is whether scrolling should merely start an SVG animation or should continuously control its progress. Those are different interaction models. A reveal that begins when an icon enters the viewport needs a trigger. A route line that draws forward and backward with the page needs a scroll timeline. A pinned, scrubbed sequence with several coordinated elements usually needs an orchestration tool.

What the user should experience

Best first approach

Why

Animate once when the SVG enters view

IntersectionObserver plus CSS

Widely available, small, and easy to enhance progressively

Make progress follow an element through the viewport

CSS animation-timeline: view()

The browser supplies a view-progress timeline without a scroll listener

Make progress follow an entire scroll container

CSS animation-timeline: scroll()

The scroller itself becomes the timeline

Pin, scrub, snap, or coordinate a multi-step scene

GSAP ScrollTrigger

A mature timeline model is easier to reason about than many hand-wired callbacks

This decision prevents the most common beginner mistake: adding a large animation system to solve a visibility trigger, or trying to simulate a scroll-linked timeline with a stream of scroll events. If you are choosing between CSS and GSAP more broadly, use the CSS vs GSAP decision guide before committing the project architecture.

Pattern 1: trigger an SVG animation when it enters view

Use this pattern for feature icons, diagrams, logos, and short line draws that should play once when the reader reaches them. The IntersectionObserver API asynchronously reports when a target crosses a configured visibility threshold. It has been widely available across browsers since 2019 and one observer can watch many targets.

Start with meaningful, visible SVG

The SVG should be complete and understandable before JavaScript runs. That protects the content when scripting fails, when a crawler renders a static state, and when a visitor requests less motion. Give an informative illustration an accessible name and description; hide decorative SVG from assistive technology instead. The accessible SVG guide explains the distinction in detail.

<figure class="feature-graphic">
  <svg
    class="feature-graphic__svg"
    viewBox="0 0 240 120"
    role="img"
    aria-labelledby="feature-title feature-desc"
  >
    <title id="feature-title">Three connected workflow steps</title>
    <desc id="feature-desc">A line connects plan, animate, and test.</desc>
    <path class="feature-graphic__route" d="M24 60 H216" pathLength="1" />
    <circle cx="24" cy="60" r="12" />
    <circle cx="120" cy="60" r="12" />
    <circle cx="216" cy="60" r="12" />
  </svg>
  <figcaption>Plan, animate, then test the interaction.</figcaption>
</figure>

Hide the start state only when motion is available

Do not make the default CSS invisible. Add an enhancement class only after the browser and the user preference have been checked. That way the safe failure mode is finished, visible content.

.feature-graphic__svg {
  opacity: 1;
  transform: translateY(0);
}

.motion-ready .feature-graphic__svg {
  opacity: 0;
  transform: translateY(1rem);
  transition: opacity 500ms ease, transform 500ms ease;
}

.motion-ready .feature-graphic__svg.is-visible {
  opacity: 1;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  .feature-graphic__svg {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

Observe once, reveal, and stop observing

The observer callback should do as little work as possible. When a target becomes visible, add the state class and call unobserve() if the effect should not replay. Reusing one observer for the whole group is simpler than constructing one observer per SVG.

const reduceMotion = window.matchMedia(
  "(prefers-reduced-motion: reduce)"
).matches;

const graphics = document.querySelectorAll(".feature-graphic__svg");

if (reduceMotion || !("IntersectionObserver" in window)) {
  graphics.forEach((graphic) => graphic.classList.add("is-visible"));
} else {
  document.documentElement.classList.add("motion-ready");

  const observer = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
      if (!entry.isIntersecting) return;

      entry.target.classList.add("is-visible");
      observer.unobserve(entry.target);
    });
  }, {
    threshold: 0.25,
    rootMargin: "0px 0px -10% 0px"
  });

  graphics.forEach((graphic) => observer.observe(graphic));
}

A threshold of 0.25 means roughly one quarter of the target must intersect the root before the callback crosses that threshold. The negative bottom root margin delays the reveal slightly. Treat both values as design inputs: test them with short screens, browser zoom, mobile address bars, and unusually tall SVGs.

Pattern 2: let CSS map SVG progress to scrolling

Use a scroll-driven CSS animation when the visual state should follow the user's position rather than merely start at a threshold. The current MDN scroll-driven animations guide distinguishes two useful timelines:

  • view() creates a view-progress timeline from a subject moving through its nearest scroll container.
  • scroll() creates a scroll-progress timeline from the scroll position of a chosen scroller and axis.

For a path that draws as its own SVG enters and crosses the viewport, view() is the natural starting point. Normalize the path with pathLength="1", keep the completed stroke as the baseline, and place the animated start state inside a feature query.

<svg viewBox="0 0 320 120" role="img" aria-labelledby="route-title">
  <title id="route-title">Route from research to release</title>
  <path
    class="scroll-route"
    pathLength="1"
    d="M20 96 C96 8 224 8 300 96"
  />
</svg>
.scroll-route {
  fill: none;
  stroke: currentColor;
  stroke-width: 6;
  stroke-linecap: round;
  stroke-dasharray: 1;
  stroke-dashoffset: 0;
}

@supports (animation-timeline: view()) {
  .scroll-route {
    stroke-dashoffset: 1;
    animation: draw-route linear both;
    animation-timeline: view();
    animation-range: entry 15% cover 55%;
  }

  @keyframes draw-route {
    to {
      stroke-dashoffset: 0;
    }
  }
}

@media (prefers-reduced-motion: reduce) {
  .scroll-route {
    animation: none;
    stroke-dashoffset: 0;
  }
}

Declare animation-timeline after the animation shorthand. The shorthand resets timeline-related values, so reversing the order silently returns the animation to the normal document timeline. The animation-range declaration then limits the useful portion of the view timeline: this example begins after entry has started and finishes while the element covers the viewport.

Use scroll(root block) instead when a page-level progress indicator should reflect the document's vertical scroll range. Use scroll(self inline) for an element whose own horizontal scroll position should drive the animation. If the selected axis has no overflow, the timeline is inactive.

Browser capabilities continue to move, so feature queries and real-device tests remain part of production work. For a deeper implementation focused on the newer time-based trigger syntax, see CSS scroll-triggered SVG animation with timeline-trigger.

Pattern 3: use GSAP ScrollTrigger for choreographed scenes

Choose ScrollTrigger when the interaction needs pinning, scrubbing, snapping, callbacks, responsive rebuilds, or several SVG parts coordinated on one timeline. The official ScrollTrigger documentation covers those controls. A library is justified here because the requirement is orchestration, not because SVG itself needs a special animation engine.

The following pattern keeps the artwork complete for reduced-motion users and creates a scrubbed timeline only when motion is allowed. Import paths depend on the project's build setup; confirm them against the installed GSAP package.

import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";

gsap.registerPlugin(ScrollTrigger);

const media = gsap.matchMedia();

media.add("(prefers-reduced-motion: no-preference)", () => {
  const timeline = gsap.timeline({
    scrollTrigger: {
      trigger: ".process-map",
      start: "top 75%",
      end: "bottom 35%",
      scrub: true
    }
  });

  timeline
    .from(".process-map__route", {
      strokeDashoffset: 1,
      ease: "none"
    })
    .from(".process-map__node", {
      opacity: 0,
      scale: 0.8,
      stagger: 0.12
    }, 0.1);
});

Use the line-drawing preparation from How SVG line drawing animation works, including a normalized pathLength. For complex responsive layouts, refresh measurements after fonts, images, and layout-affecting content settle. Avoid pinning by default: it changes document behavior and deserves keyboard, zoom, mobile viewport, and content-overflow tests.

A production decision process that scales

1. Write the interaction in one sentence

Use an observable outcome: “draw the route once when 25% is visible,” “map the route to the element's passage through the viewport,” or “scrub three phases while the diagram remains in place.” If the sentence mixes several behaviors, split the experience into states before writing code.

2. Define the static and reduced-motion result

Decide what remains when animation is removed. Essential labels, relationships, and actions must still be available. Reduced motion usually means showing the completed result, shortening a transition, or replacing spatial movement with a restrained opacity change. It should not mean hiding content. Use the site's prefers-reduced-motion guide as the shipping checklist.

3. Pick the smallest control model that expresses the behavior

IntersectionObserver is a threshold signal. CSS view and scroll timelines are progress signals. ScrollTrigger is a coordination system. Select by behavior and maintenance cost, not by novelty.

4. Measure the exact element that matters

Nested scroll containers are a frequent source of surprises. IntersectionObserver uses its configured root; view() follows the nearest ancestor scroller; scroll() follows the requested scroller; ScrollTrigger uses its configured trigger and scroller. Confirm which box and axis control the effect before tuning numbers.

5. Verify the result without animation

Disable JavaScript, emulate reduced motion, and force the feature query to fail. In every case the reader should still understand the figure and continue through the page.

Performance rules for scroll-linked SVG

  • Prefer composited changes for entrances. Opacity and transforms are usually safer starting points than animating geometry or layout-affecting properties.
  • Keep path work proportional. A single meaningful route draw is easier to render and understand than dozens of continuously changing paths.
  • Avoid a raw scroll handler. Do not read layout and write styles on every scroll event when an observer, CSS timeline, or established library already models the behavior.
  • Pause work that is no longer useful. Unobserve one-shot entrances. Kill or rebuild library timelines when their component is removed or its media query no longer matches.
  • Test the real page. SVG complexity, filters, shadows, clipping, sticky ancestors, and surrounding layout determine cost. A tiny isolated demo is not a performance guarantee.

Profile representative low-end hardware and long pages. Check the main thread, paint activity, layer count, and memory while scrolling normally and rapidly. The SVG animation performance guide provides a wider audit process.

Common failures and how to diagnose them

The SVG is invisible when JavaScript fails

The hidden start state was applied unconditionally. Make the finished state the default and add the start state only after capability and preference checks pass.

The CSS animation runs on time instead of scroll

Check declaration order. An animation shorthand placed after animation-timeline resets the timeline. Also confirm that the chosen scroller actually overflows on the selected axis.

The reveal fires too early or too late

Inspect the observer's root, threshold, and root margin. Then test different viewport heights and zoom levels. A fixed offset that feels right on one laptop may fail on a mobile landscape viewport.

The path jumps instead of drawing smoothly

Confirm that stroke-dasharray and stroke-dashoffset use the same normalized scale. Adding pathLength="1" makes a value of 1 represent the whole path, which is easier to maintain across artwork revisions.

The ScrollTrigger position changes after load

Late fonts, responsive images, accordions, and injected content may change layout after measurements were taken. Ensure content dimensions are stable, then refresh the trigger after intentional layout changes. Treat repeated manual refreshes as a signal to fix unstable layout.

Reduced motion still scrubs the artwork

The motion preference was used only for CSS. Gate JavaScript timelines with the same media query and make their no-motion state explicit. Test the operating-system preference before page load and while the page is already open.

Testing checklist before release

  • Keyboard and screen-reader users receive the same meaning and controls without depending on movement.
  • Reduced-motion mode shows a complete, readable result.
  • JavaScript disabled and unsupported CSS both leave the SVG visible.
  • Mobile portrait, mobile landscape, 200% zoom, and nested scrollers activate at sensible points.
  • Scrolling forward and backward produces the intended state without flicker.
  • Back-forward cache restoration and in-page navigation do not leave stale classes or timelines.
  • Long tasks, paint cost, and layout shifts stay acceptable on a representative lower-end device.
  • The effect does not hijack scrolling, trap focus, or require precision movement.

Frequently asked questions

What is the easiest scroll-triggered SVG animation for a beginner?

Use IntersectionObserver to add a class when the SVG enters the viewport, then let CSS handle a short transform, opacity, or stroke animation. It teaches the trigger model without coupling animation progress to every scroll position.

What is the difference between scroll-triggered and scroll-driven animation?

A scroll-triggered animation starts after a condition is met and then runs on time. A scroll-driven animation uses scroll position as its timeline, so progress can move forward and backward with scrolling.

Should I use view() or scroll()?

Use view() when the subject's passage through its nearest scrollport should control progress. Use scroll() when the scroll position of a chosen container or the page should control progress.

Do I need GSAP for SVG scroll animation?

No. IntersectionObserver and CSS cover many production effects. GSAP becomes valuable when you need a coordinated timeline, scrubbed sequencing, pinning, snapping, or reliable lifecycle controls across a larger interaction.

Can an SVG loaded with img animate on scroll?

You can animate the outer image element as a box, but page CSS and JavaScript cannot directly target paths inside an SVG loaded through img. Use inline SVG when individual groups or paths need separate animation.

Is scroll animation accessible?

It can be, if meaning does not depend on motion, the final state is available without animation, controls remain operable, and the implementation honors prefers-reduced-motion. Avoid unexpected large movement and scroll hijacking.

Official references and next steps

Build the smallest version first, verify the no-motion result, and then add only the control the interaction truly needs. For the broader foundations, continue with your first SVG animation with CSS or the practical GSAP beginner guide.

Sources and further reading

Continue learning
Guide

SVG Animation with the View Transition API: 2026 Guide

Build accessible SVG transitions with the View Transition API. Covers browser support, same- and cross-document patterns, fallbacks, performance, and testing.