Making Complex SVG Animations with GSAP
#gsap
#svg
#animation
#frontend
SVG plus GSAP is a powerful combination for complex, high-performance motion design on the web. SVG gives you resolution-independent graphics and advanced features like masks, filters, and gradients; GSAP gives you robust, ergonomic control over sequencing, easing, and performance.
This guide walks through a modular approach to building complex SVG animations with GSAP using only free plugins. You’ll learn how to orchestrate multiple effects, animate along paths, draw strokes without paid plugins, and keep everything responsive and accessible.
Why GSAP for SVG
- Fine-grained control with timelines, labels, and callbacks
- Consistent cross-browser transforms, filters, and color tweens
- Strong performance and precise easing
- Plugins for common patterns like motion paths
Project setup Include GSAP and MotionPathPlugin via CDN:
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/MotionPathPlugin.min.js"></script>
We’ll build a single self-contained demo you can paste into an HTML file.
HTML and SVG This example creates an orbital scene: a craft travels along an elliptical path, stars twinkle, the orbit line “draws” itself, and the craft glows. Everything scales via viewBox.
<style>
/* Demo layout only */
.stage {
max-width: 820px;
margin: 2rem auto;
background: #0b1020;
border-radius: 12px;
padding: 1rem;
}
svg {
width: 100%;
height: auto;
display: block;
}
.label {
color: #9fb1ff;
font: 14px/1.4 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
max-width: 820px;
margin: 0.5rem auto 2rem;
}
</style>
<div class="stage">
<svg viewBox="0 0 600 400" aria-labelledby="title desc" role="img">
<title id="title">Orbital SVG Animation</title>
<desc id="desc">A craft flies along an orbit while stars twinkle and the orbit line draws itself.</desc>
<defs>
<!-- Background gradient -->
<radialGradient id="bg" cx="50%" cy="45%" r="80%">
<stop offset="0%" stop-color="#0b1a3a"/>
<stop offset="100%" stop-color="#060a18"/>
</radialGradient>
<!-- Orbit gradient stroke -->
<linearGradient id="orbitGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop id="orbitStop1" offset="0%" stop-color="#2dd4ff"/>
<stop id="orbitStop2" offset="100%" stop-color="#7c3aed"/>
</linearGradient>
<!-- Glow filter for the craft -->
<filter id="glow" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur id="glowBlur" stdDeviation="0" result="b"/>
<feMerge>
<feMergeNode in="b"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<!-- Background -->
<rect width="600" height="400" fill="url(#bg)"/>
<!-- Orbit path -->
<path id="orbit"
d="M 300,200 m -180,0
a 180,120 0 1,0 360,0
a 180,120 0 1,0 -360,0"
fill="none"
stroke="url(#orbitGrad)"
stroke-width="3"
stroke-linecap="round"
opacity="0.8"/>
<!-- Stars -->
<g id="stars" fill="#f8fbff" opacity="0.8">
<circle class="star" cx="70" cy="60" r="1.6"/>
<circle class="star" cx="130" cy="110" r="1.3"/>
<circle class="star" cx="510" cy="80" r="1.4"/>
<circle class="star" cx="420" cy="50" r="1.2"/>
<circle class="star" cx="520" cy="320" r="1.8"/>
<circle class="star" cx="90" cy="300" r="1.5"/>
<circle class="star" cx="270" cy="40" r="1.1"/>
<circle class="star" cx="320" cy="360" r="1.2"/>
</g>
<!-- Craft -->
<g id="rocket" filter="url(#glow)">
<!-- Body -->
<g id="craft" transform="translate(300,200)">
<ellipse cx="0" cy="0" rx="12" ry="20" fill="#e2e8ff" stroke="#9aa4ff" stroke-width="1"/>
<circle cx="0" cy="-3" r="4" fill="#7c3aed"/>
<!-- Fins -->
<path d="M -12,5 l -10,10 l 10,-4 z" fill="#6472ff"/>
<path d="M 12,5 l 10,10 l -10,-4 z" fill="#6472ff"/>
<!-- Flame -->
<path id="flame" d="M 0,20 c -3,8 3,8 0,16 c 6,-8 -6,-8 0,-16 z" fill="#ffb703" opacity="0.85"/>
</g>
</g>
</svg>
</div>
<p class="label">Tip: Everything sits in a single SVG with a responsive viewBox. We’ll control it with GSAP timelines.</p>
Core GSAP timeline We’ll orchestrate three phases: draw the orbit stroke, fly the craft along the path, then pulse glow and twinkle stars.
<script>
// Register plugin
gsap.registerPlugin(MotionPathPlugin);
// Utilities
function setStrokeDraw(path) {
const len = path.getTotalLength();
gsap.set(path, {
strokeDasharray: len,
strokeDashoffset: len
});
return len;
}
function prefersReducedMotion() {
return window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}
// Main
window.addEventListener("DOMContentLoaded", () => {
const orbit = document.getElementById("orbit");
const rocket = document.getElementById("rocket");
const glowBlur = document.getElementById("glowBlur");
const stars = gsap.utils.toArray(".star");
const len = setStrokeDraw(orbit);
gsap.set(rocket, { xPercent: -50, yPercent: -50, transformOrigin: "50% 50%" });
// Global defaults and reduced-motion
const reduce = prefersReducedMotion();
const base = { ease: "power2.inOut" };
const speed = reduce ? 0.5 : 1; // slow down if reduced motion
const tl = gsap.timeline({ defaults: base });
// 1) Reveal the orbit by animating the stroke offset
tl.addLabel("reveal")
.to(orbit, {
strokeDashoffset: 0,
duration: 2.0 / speed
}, "reveal");
// 2) Fly the craft along the orbit with alignment and autorotate
tl.addLabel("flight", "<0.3")
.to(rocket, {
duration: 5.0 / speed,
ease: "none",
motionPath: {
path: orbit,
align: orbit,
autoRotate: true,
alignOrigin: [0.5, 0.5]
}
}, "flight");
// 3) Twinkle stars during the flight
tl.to(stars, {
duration: 0.25 / speed,
scale: 1.5,
opacity: 1,
yoyo: true,
repeat: 1,
transformOrigin: "50% 50%",
stagger: {
each: 0.08 / speed,
from: "random"
}
}, "flight+=0.4");
// 4) Pulse glow via filter during key parts of the flight
tl.to(glowBlur, {
duration: 0.35 / speed,
attr: { stdDeviation: 3 },
yoyo: true,
repeat: 5
}, "flight+=0.2");
// 5) Animate gradient stop offsets for subtle chromatic motion
// This shifts the gradient along the stroke without paid plugins
tl.to("#orbitStop1", {
duration: 2.5 / speed,
attr: { offset: 0.2 }
}, "reveal")
.to("#orbitStop2", {
duration: 2.5 / speed,
attr: { offset: 0.9 }
}, "reveal");
// 6) Flame flicker loop (separate timeline)
const flameTL = gsap.timeline({ repeat: -1, repeatDelay: 0, defaults: { ease: "sine.inOut" } });
flameTL
.to("#flame", { duration: 0.08 / speed, scaleY: 1.15, scaleX: 0.95, transformOrigin: "50% 0%" })
.to("#flame", { duration: 0.12 / speed, scaleY: 0.9, scaleX: 1.05 }, "<")
.to("#flame", { duration: 0.10 / speed, scaleY: 1.0, scaleX: 1.0 }, "<");
// Accessibility: If reduced motion is requested, simplify
if (reduce) {
// Pause the flame loop and reduce glow pulses
flameTL.pause();
gsap.set(glowBlur, { attr: { stdDeviation: 1 } });
}
// Optional: controls for debugging
// window.master = tl; // expose timeline in console
});
</script>
Key techniques explained
- Drawing strokes without paid plugins: Use getTotalLength(), then animate strokeDashoffset from the path length down to 0. This replicates DrawSVGPlugin’s core effect without the plugin.
- Motion along paths: MotionPathPlugin aligns and auto-rotates the craft to follow the tangent of the orbit. When using autoRotate with SVG, set xPercent/yPercent and alignOrigin to center the group.
- Filter animation: Many filter attributes can animate via attr. Pulsing feGaussianBlur’s stdDeviation gives a glow effect.
- Gradient animation: You can animate stop-color and offset via attr to create subtle movement across long strokes.
- Timelines and labels: Labels like “reveal” and “flight” let you overlap and orchestrate phases without manual durations.
Modular timelines for complex scenes As your animation grows, compose smaller timelines and then nest them in a master. This improves readability and makes it easier to reuse effects.
function buildOrbitReveal(path) {
const tl = gsap.timeline();
const len = path.getTotalLength();
gsap.set(path, { strokeDasharray: len, strokeDashoffset: len });
return tl.to(path, { strokeDashoffset: 0, duration: 2, ease: "power2.inOut" });
}
function buildFlight(rocket, path) {
return gsap.timeline().to(rocket, {
duration: 5,
ease: "none",
motionPath: { path, align: path, autoRotate: true, alignOrigin: [0.5, 0.5] }
});
}
// Usage
const master = gsap.timeline();
master.add(buildOrbitReveal(document.getElementById("orbit")));
master.add(buildFlight(document.getElementById("rocket"), document.getElementById("orbit")), "-=0.3");
Advanced SVG animation ideas (all GSAP-core friendly)
- Mask and clipPath reveals: Animate the transform or path of a mask or clipPath to reveal artwork progressively. Keep mask shapes simple for performance.
- Parallax within SVG: Wrap layers in groups and animate their transforms at different rates. Combine with MotionPath for complex depth.
- Coordinated color systems: Drive hues with CSS variables on the root, and tween those variables with GSAP to recolor multiple SVG parts in sync.
- Event-driven interactions: Hover to pause/resume a sub-timeline, click to reverse or play segments using tl.pause(), tl.resume(), tl.reverse(), or tl.tweenTo(“label”).
Performance tips
- Prefer transforms over attributes where possible (translate/rotate/scale on groups instead of editing raw path points).
- Keep filter regions tight. Wide filter bounding boxes are expensive; adjust filter x/y/width/height to the minimum necessary coverage.
- Avoid excessive numbers of small elements. Consolidate shapes or render subtle starfields as patterns or symbols reused via
- Use viewBox for responsive scaling, not large fixed widths. Avoid animating width/height directly.
- Cache expensive states if you must animate attributes or filters repeatedly.
Accessibility and user controls
- Respect prefers-reduced-motion: slow down or skip non-essential motion.
- Provide states that don’t rely solely on animation to convey meaning.
- Consider adding Play/Pause controls for longer sequences.
Troubleshooting checklist
- Unexpected path alignment: Ensure the animated object is centered with xPercent/yPercent and alignOrigin when using MotionPathPlugin align.
- Blurry output when scaled: SVG scales crisply, but heavy filters blur. Reduce stdDeviation or confine filter regions.
- Color attribute tweening fails: Use attr for stop-color and other raw attributes: { attr: { “stop-color”: “#xxxxxx” } }.
Where to go next
- Break your animation into cohesive modules and return timelines from small builder functions.
- Add ScrollTrigger for timeline scrubbing tied to scroll position if your design calls for it.
- If you need true path morphing between arbitrary shapes, consider purpose-built tools. GSAP can animate attributes, but precise morphing requires consistent point counts and often dedicated utilities.
With the patterns above—stroke drawing, motion paths, filter pulses, gradient shifts, and modular timelines—you can build complex, performant SVG animations with precise control and clean structure, all with GSAP’s core toolset.