Motion Path Plane — Free GSAP MotionPathPlugin Snippet
Motion Path Plane · Animations · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<div class="mpp-wrap">
<svg class="mpp-svg" viewBox="0 0 600 320">
<path id="mppPath" class="mpp-path" d="M60,240 C140,80 240,80 300,180 C360,280 460,280 540,110"/>
<circle class="mpp-dot" cx="60" cy="240" r="6"/>
<circle class="mpp-dot" cx="540" cy="110" r="6"/>
<g id="mppPlane" class="mpp-plane"><text font-size="30" text-anchor="middle" dominant-baseline="central">✈️</text></g>
</svg>
<div class="mpp-bar">
<button class="mpp-btn" data-speed="0.5">0.5×</button>
<button class="mpp-btn is-active" data-speed="1">1×</button>
<button class="mpp-btn" data-speed="2">2×</button>
</div>
<p class="mpp-hint">autoRotate keeps the plane's nose on the curve's tangent.</p>
</div>*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#0b0d16;color:#fff;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.mpp-wrap{display:flex;flex-direction:column;align-items:center;gap:16px;width:min(640px,94vw)}
.mpp-svg{width:100%;height:auto;background:radial-gradient(90% 90% at 50% 30%,#141a30,#0b0d16);border-radius:18px;border:1px solid rgba(255,255,255,.1)}
.mpp-path{fill:none;stroke:rgba(165,180,252,.5);stroke-width:2.5;stroke-dasharray:3 9;stroke-linecap:round}
.mpp-dot{fill:#818cf8}
.mpp-plane{will-change:transform}
.mpp-bar{display:flex;gap:8px}
.mpp-btn{padding:8px 18px;border-radius:99px;border:1px solid rgba(255,255,255,.16);background:#141a2e;color:#c9d2f8;font:600 13px system-ui;cursor:pointer;transition:background .2s,border-color .2s}
.mpp-btn:hover{background:#1d2440}
.mpp-btn.is-active{border-color:#818cf8;background:#1d2440}
.mpp-hint{color:#5f6782;font-size:12px;letter-spacing:.05em}gsap.registerPlugin(MotionPathPlugin);
// One tween: the plane follows the SVG path, auto-rotating to the
// curve's tangent. align maps path coordinates onto the plane's space;
// alignOrigin centers the plane's own box on the path line.
var flight = gsap.to('#mppPlane', {
duration: 5,
repeat: -1,
yoyo: true,
ease: 'power1.inOut',
motionPath: {
path: '#mppPath',
align: '#mppPath',
autoRotate: true,
alignOrigin: [0.5, 0.5]
}
});
// Speed controls scale playback without rebuilding the tween.
var buttons = document.querySelectorAll('.mpp-btn');
buttons.forEach(function (btn) {
btn.addEventListener('click', function () {
buttons.forEach(function (b) { b.classList.remove('is-active'); });
btn.classList.add('is-active');
gsap.to(flight, { timeScale: Number(btn.getAttribute('data-speed')), duration: 0.5 });
});
});Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Motion Path Plane — Fly Any Element Along an SVG Curve

Moving an element along a curve is the animation ask that breaks CSS: keyframes interpolate in straight lines, and offset-path support is inconsistent where you need it most. GSAP's MotionPathPlugin (free on the CDN) makes it one tween: this snippet flies a plane back and forth along a dashed bézier route, nose always tangent to the curve, with buttons that retime the flight live via timeScale.
One config replaces a page of trigonometry
motionPath: { path: '#mppPath', align: '#mppPath', autoRotate: true, alignOrigin: [0.5, 0.5] } does four jobs. path samples the bézier; autoRotate continuously sets rotation to the curve's tangent angle (the derivative work you'd otherwise approximate by sampling two nearby points, as the hand-rolled scroll path follow does); align solves coordinate spaces; and alignOrigin pins the element's center — rather than its top-left corner — onto the path line. Without alignOrigin, the plane would ride the curve by its corner, visibly floating beside the route.
align is the part everyone underestimates
An SVG path's coordinates live in the SVG's viewBox space; the animated element has its own coordinate system, transforms, and origin. align: '#mppPath' measures both and calibrates the tween so the element travels *visually on* the stroked line — accounting for viewBox scaling, existing transforms, and nesting. This is the plugin's quiet superpower: it even lets DOM elements (a div outside the SVG) follow an SVG path, since alignment is computed in screen space.
yoyo makes a patrol route out of one path
repeat: -1, yoyo: true sends the plane out and back forever, and because autoRotate derives from direction of travel, the plane automatically faces backward on return legs — no second path, no manual flip. power1.inOut eases each leg so the plane banks gently out of each endpoint rather than bouncing off it.
timeScale is live retiming, not rebuilding
The speed buttons never touch the motion path: they tween the *tween's* timeScale to 0.5, 1, or 2 over half a second. Animating timeScale (rather than setting it) means speed changes themselves accelerate smoothly — the plane audibly "throttles up" instead of jumping to the new rate. This works because a GSAP tween is itself tweenable, one of the engine's most underused properties.
The route is honest markup
The dashed stroke users see *is* the animation's source of truth — restyle it, and the flight follows any d you paste in from Figma or Illustrator. Endpoint dots are plain circles at the path's terminal coordinates. Nothing about the route lives in JavaScript.
start, end, and partial journeys
Beyond this demo: start/end fractions animate along a sub-segment (start: 0.25, end: 0.75), values beyond 1 loop around closed paths, and start > end reverses direction — all without editing the path. Pair with ScrollTrigger and the same tween scrubs with the page.
Customizing it
Swap the emoji for an SVG plane that points right (autoRotate assumes rightward-facing art; add a fixed rotation offset otherwise), draw your own route, or attach a drawn trail with scroll svg path draw mechanics. Related: scroll-scrubbed path riding in scroll path follow, orbital motion in orbiting icons, and route-based storytelling in scroll timeline dots.
Step by step
How to Use
- 1Add the GSAP CDNsInclude gsap and MotionPathPlugin from the CDN panel.
- 2Paste HTML, CSS, and JSThe plane starts flying its dashed route.
- 3Watch the turnsautoRotate keeps the nose on the tangent.
- 4Let it returnyoyo flies the same path backward, heading flipped.
- 5Click 2×timeScale tweens up — the plane throttles smoothly.
- 6Paste your own pathAny d attribute becomes the new route.
Real-world uses
Common Use Cases
autoRotate deriving the heading from the curve tangent.align calibrating SVG space to the element.alignOrigin pinning the element's middle to the line.Got questions?
Frequently Asked Questions
offset-path can follow a path string, but support gaps (especially older Safari), no autoRotate offset control, awkward coordinate handling for elements outside the SVG, and zero runtime control (no timeScale, no scrubbing, no partial segments) make it fragile for production. MotionPathPlugin normalizes all of it behind one tween that also composes with every other GSAP feature.
It reconciles coordinate systems: the path's points live in the SVG viewBox space while the moving element has its own space, transforms, and origin. align: '#mppPath' measures both in screen space and calibrates the tween so the element rides visually on the stroked line — which is also why a plain div outside the SVG can follow an SVG path perfectly.
autoRotate: true sets the element's rotation to the path's tangent angle at every tick — the same heading math you'd otherwise approximate by sampling two nearby points and taking atan2. Because heading derives from travel direction, the yoyo return leg faces backward automatically. Art must point right by default; autoRotate: 90 style numeric offsets fix art drawn facing up.
They tween the tween: gsap.to(flight, { timeScale: 2, duration: 0.5 }) smoothly accelerates playback rate itself, so the plane throttles up over half a second instead of jumping. Tweens being tweenable is core GSAP — the flight's position, path, and heading logic are untouched; only its clock changes.
Yes — start and end fractions define a sub-segment (start: 0.25, end: 0.75 flies the middle half), start greater than end reverses direction, and on closed paths values beyond 1 wrap for continuous laps. Combined with ScrollTrigger, the same motionPath tween scrubs along the route as the page scrolls.
Register the plugin at module scope, inline the SVG, and create the flight tween in a mount effect — useEffect, onMounted, or ngAfterViewInit — via refs, killing it (or reverting a gsap.context) in the cleanup so the infinite repeat stops on unmount. Keep speed as a ref-held tween handle, not state — buttons call gsap.to(flightRef.current, { timeScale }) directly. The control bar maps straight to Tailwind.