You Might Also Like
Scroll Path Follow — Free GSAP SVG Motion Snippet
Scroll Path Follow · Scroll · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Scroll Path Follow — Move an Element Along an SVG Path With Scroll

The scroll path follow sends an element — here a rocket — traveling along a curved SVG path as the page scrolls, rotating to face its direction of travel while the path draws itself behind it and milestone dots light up as they're passed. It's the backbone of journey maps, process trails, and delivery-route sections. This snippet builds it with GSAP ScrollTrigger (from a CDN) and the SVG geometry API — no MotionPathPlugin required.
getPointAtLength is the whole positioning engine
SVG paths expose their geometry: getTotalLength() returns the curve's true arc length, and getPointAtLength(d) returns the exact x/y coordinate at distance d along it. The snippet tweens a single number — state.d from 0 to the path length — and on every update asks the path where that distance lands, then writes a translate() transform on the ship group. The browser does all the Bézier math; the animation code never touches curve equations.
Heading comes from sampling two points
To rotate the rocket into its direction of travel, the render step samples the path twice — at d and at d + 2 — and takes Math.atan2 of the difference for the tangent angle. This two-point finite difference is the standard way to get a heading without derivative math, and the 2-unit lookahead is small enough to hug tight curves. A +45° offset compensates for the rocket emoji's natural up-right orientation.
The trail is the same dash trick, sharing the same distance
A second copy of the path sits on top with stroke-dasharray and stroke-dashoffset set to the full length (invisible). Each frame sets its offset to LEN − state.d — so the drawn portion always ends exactly under the ship. Because trail and ship both derive from one number, they can never desynchronize, no matter how fast the user scrubs.
Milestone dots light up by distance thresholds
Each stop's distance along the path is precomputed as a fraction of LEN; the render loop toggles an is-hit class when state.d passes it (with a 4-unit tolerance so the dot fires as the ship touches it, not after). CSS transitions handle the fill/stroke pop, keeping per-frame JS to class toggles.
Unpinned on purpose
Unlike most scroll-driven effects, this one doesn't pin: the journey is laid out in normal document flow, and the trigger maps top 70% → bottom 45% of the section's passage through the viewport onto the tween. The rocket travels as the page travels — appropriate for a tall roadmap section where labels sit beside the curve at fixed document positions. scrub: 0.5 smooths wheel steps into glides.
The viewBox makes it responsive for free
All geometry lives in a 600×1400 viewBox, so the same path coordinates work at any rendered size — getPointAtLength returns viewBox units and the transform applies in the same space. No resize listeners needed.
Customizing it
Redraw the d attribute in any editor (both path copies must match), move the stops, or swap the rocket for a truck, plane, or dot. Related: scroll SVG path draw for the line-only version, scroll timeline dots for a straight-rail variant, and vertical timeline for a static cousin.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to work out the SVG geometry math on your own. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly how getPointAtLength turns a single tweened distance into both the ship's position and the trail's stroke-dashoffset, or why the heading angle is computed from two nearby sample points instead of a derivative formula. The same assistant can help optimize it — asking whether the 2-unit lookahead for tangent sampling should scale with path curvature, or whether milestone hit-testing could be replaced with a cheaper lookup for a path with many stops. It's also useful for extending the effect: ask it to add a second object trailing behind the first at a fixed distance offset, make milestones trigger a popup or sound on hit, or swap the rocket for an SVG icon that itself needs to be pre-rotated to match the path's starting tangent. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a "scroll path follow" effect in plain HTML, CSS, and JavaScript using GSAP with its ScrollTrigger plugin (load both from a CDN) and native SVG geometry methods — do not use MotionPathPlugin.
Requirements:
- An inline SVG containing a curved path (drawn with cubic Bezier commands), a second identical copy of that same path used only for a trailing draw effect, several small circles marking milestone stops along the curve, and a group element representing a moving object (e.g. an emoji or icon) that will travel along the path.
- On load, call getTotalLength on the track path to get its true arc length, and set the trail-copy path's stroke-dasharray and stroke-dashoffset both to that full length so it starts completely undrawn.
- Precompute each milestone's target distance along the path as a fraction of the total length (not by guessing pixel positions).
- Tween a single plain object's distance property from 0 to the path's total length using GSAP with ease none, driven by a ScrollTrigger with scrub enabled — the object must NOT be pinned; instead the trigger should map the distance the section travels through the viewport (e.g. from when its top nears the viewport to when its bottom passes a point near the top) onto the tween.
- On every update of that tween: call getPointAtLength on the track path at the current distance to position the moving object via a transform; sample a second point slightly further along the path and use Math.atan2 on the difference between the two points to compute a heading angle, then rotate the moving object to face that heading (adding any constant offset needed if the icon doesn't natively point along the positive x-axis); set the trail path's stroke-dashoffset to (total length minus current distance) so the drawn stroke always ends exactly at the moving object's current position; and toggle a css class on each milestone circle when the current distance passes that milestone's precomputed distance (with a small tolerance).
- Confirm scrolling back up moves the object backward along the path, un-draws the trail, and un-marks milestones, purely because the tween is scrubbed in reverse — no separate reverse logic.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Add the GSAP CDNsInclude gsap and ScrollTrigger from the CDN panel.
- 2Paste HTML, CSS, and JSA dotted track, trail path, stops, and rocket render.
- 3Scroll the pageThe rocket rides the curve as the section passes.
- 4Watch the milestonesDots light up the moment the ship touches them.
- 5Scroll back upThe rocket flies home and the trail un-draws.
- 6Redraw the routeEdit the path d attribute in both path copies.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
GSAP tweens one number — a distance along the path — and every update calls the SVG API getPointAtLength(d) to get the exact x/y at that distance, writing it into the ship's transform. The browser resolves all the Bézier math internally, so the follow logic is a dozen lines and needs no plugin.
Each frame samples the path at d and d + 2 and takes Math.atan2 of the difference — a finite-difference tangent that approximates the curve's direction. The result rotates the ship group, plus a +45° constant because the rocket emoji natively points up-right. Smaller lookaheads hug tighter curves; larger ones smooth jittery headings.
Both derive from the same tweened distance: the ship's position is getPointAtLength(d) and the trail's stroke-dashoffset is LEN − d, so the drawn stroke always terminates exactly under the ship. There's no second animation to fall out of phase — one number is the single source of truth for position, trail, heading, and milestone checks.
Edit the d attribute — draw a path in Figma or Illustrator and paste it into both the track and drawn paths (they must be identical twins). getTotalLength adapts automatically. Reposition the milestone circles onto the new curve and adjust their stopDist fractions to match where along the length each stop sits.
Inline the SVG and run the measurement plus tween in a mount effect — useEffect, onMounted, or ngAfterViewInit — since getTotalLength needs the rendered element; use refs, wrap setup in gsap.context, and revert on cleanup. Write the ship transform directly in onUpdate rather than through state, or you'll re-render every scroll frame. Labels and layout around the SVG work fine as Tailwind utilities.