More Scroll Snippets
Lottie Scroll Line Draw — Scroll-Drawn SVG Path
Lottie Scroll Line Draw · Scroll · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
How to Draw a Lottie Line on Scroll With a Trim Path, lottie-web and GSAP

The Lottie Scroll Line Draw snippet strokes a flowing wave onto the screen as the visitor scrolls, with a glowing dot riding the leading edge of the line, and erases it again on the way back up. Nothing plays on a timer — the scrollbar drives the animation frame by frame — and the whole Lottie is generated in JavaScript rather than exported from After Effects, so you can see exactly how the drawing effect is built.
Lottie is JSON, and here it is built in code
A Lottie animation is a JSON document of vector layers and keyframes. Most tutorials fetch one with lottie.loadAnimation({ path: 'anim.json' }), but this snippet constructs the JSON object directly and passes it as animationData, so there is nothing to host and nothing that can 404. A pair of tiny helpers — one for scalar keyframes, one for multi-value keyframes — assemble the animation, and both always attach the easing tangents every animated keyframe needs.
The one keyframe detail that matters most
When you author a Lottie by hand, every animated keyframe must carry its i (in) and o (out) bezier tangents. Omit them and lottie-web treats the keyframe as a hold keyframe: it never interpolates, and the value stays frozen at the first keyframe forever. For a trim animating 0 to 100 that means the line never draws. The helpers here set linear tangents with control points at 0.5/0.5 on every keyframe, which is why the line grows smoothly instead of staying blank.
The draw effect is a trim path
The wave itself is a dense polyline — a sine sampled at sixty-odd points — wrapped in a Lottie trim path (shape type tm). A trim path renders only a portion of a stroked path, from a start percentage to an end percentage. Animating the trim's end from 0 to 100 across the timeline makes the stroke look like it is being drawn by a pen. A second, faint copy of the exact same path sits behind it with no trim, so the full route is always faintly visible — the same idea as a progress track. This is the vector equivalent of animating stroke-dashoffset on an SVG path, but expressed inside the Lottie so it travels with the animation.
A dot that rides the tip
To make the draw feel alive, a filled dot follows the leading edge of the line. Because the trim end and the dot both advance linearly over the same 120 frames, the tip position is predictable: the snippet samples the wave function at regular frames and keyframes the dot's position to match. The result is a dot that appears to be pulling the line into existence as you scroll.
Autoplay off, scroll on
The animation is loaded with autoplay: false so its internal clock never runs. Instead a GSAP ScrollTrigger pins the stage and scrubs a single plain value from 0 to 1 as the visitor scrolls through a range several viewport-heights tall. A requestAnimationFrame loop reads that value every frame and calls anim.goToAndStop(p * (totalFrames - 1), true) — the true tells lottie-web the value is a frame number, and goToAndStop renders a single still frame without starting playback. Because lottie-web interpolates between keyframes, fractional frames render smoothly.
Reversible for free
Deriving the frame from one scrubbed value means scrolling up simply produces smaller values, so the line un-draws and the dot travels backward with no extra reverse-playback code. A smoothed scrub: 0.5 lets the drawing glide toward the scroll position rather than snapping to every noisy wheel tick, and the intro caption fades out the moment drawing begins. Pair it with the ring-drawing Lottie scroll scrub or a scroll typewriter for a full scroll-driven sequence.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You do not have to work out how a JSON file becomes a scroll-drawn line by hand. Paste this snippet's HTML, CSS, and JS into an AI assistant like Claude and ask it to walk through why every keyframe needs i/o tangents, what goToAndStop's second argument does, or how the trim path draws the stroke. The same assistant can help you extend it — ask it to replace the wave with your own path, add a second line that draws after the first, make the dot leave a fading trail, or load an external Lottie file instead of the inline JSON. Treat the code as a conversation starter, not a finished artifact.
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-drawn Lottie line" in plain HTML, CSS, and JavaScript using lottie-web and GSAP's ScrollTrigger plugin, both from a CDN (no bundler).
Requirements:
- A pinned full-height section with a centered container div for the Lottie, an intro caption overlay, and a live "percent drawn" read-out.
- Build the Lottie as an inline animationData JSON object (leave a comment showing how to swap it for path: 'your-animation.json'). Include small helpers that build scalar and multi-value keyframes and ALWAYS attach i/o easing tangents (linear, control points 0.5/0.5) — without them lottie-web holds the value and the animation stays blank.
- The animation is a flowing sine-wave polyline drawn on via an animated trim path (type 'tm') whose end goes 0 to 100, with a faint static copy of the path behind it as a track, plus a filled dot whose position is keyframed to ride the leading edge of the draw (sample the same wave function per frame).
- Load with lottie.loadAnimation using renderer 'svg', loop false, autoplay FALSE.
- Register a GSAP tween on a ScrollTrigger targeting the pinned section, with pin true, start at top top, scrub around 0.5, and an end several hundred percent tall, animating one value p from 0 to 1.
- In a requestAnimationFrame loop, read p and call anim.goToAndStop(p * (anim.totalFrames - 1), true), update the read-out, and fade the caption out once p passes a small threshold.
- Confirm scrolling up reverses the draw.Step by step
How to Use
- 1Load the three CDN scriptsAdd lottie.min.js, gsap.min.js, and ScrollTrigger.min.js from the CDN panel, in that order.
- 2Paste the HTML, CSS, and JSA faint wave track appears centred in a pinned stage with a live "% drawn" read-out.
- 3Scroll downThe coloured line strokes on left to right and a dot rides its leading edge, tied directly to scroll position.
- 4Scroll back upThe line un-draws exactly, because goToAndStop seeks a single frame rather than playing on a timer.
- 5Reshape the lineEdit the wave function (amplitude, cycles) or replace the generated points with your own path to draw any shape.
- 6Tune the lengthChange the ScrollTrigger end value (+=400%) for a longer, slower draw or a shorter, quicker one.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Constructing the animationData object directly keeps the snippet self-contained — there is no second network request, nothing to host, and nothing that can 404 or be blocked by a sandbox. It also makes the drawing logic visible and editable. For your own project you can load an external file instead: delete the animationData object and pass path: "your-animation.json" to loadAnimation. The scroll scrubbing is identical because the player exposes the same API either way.
Because lottie-web treats an animated keyframe with no i/o easing tangents as a hold keyframe — it never interpolates, so a trim end keyframed 0 to 100 stays stuck at 0 and the line never draws. The helper functions in this snippet attach linear tangents (control points at 0.5/0.5) to every keyframe, which is what makes the value actually move. If you hand-edit a keyframe and the animation goes blank, missing tangents are the first thing to check.
The trim end and the dot both advance linearly over the same 120 frames, so the drawn fraction at any frame is simply frame / totalFrames. The snippet samples the wave function at regular frames and keyframes the dot position to those points, so the dot sits on the leading edge of the drawn stroke throughout. If you reshape the line, regenerating the dot keyframes from the same function keeps them in sync.
play runs the animation on its internal clock, which is exactly what you do not want when scroll should control it. goToAndStop(frame, true) renders one specific still frame and never starts the timer — the second argument tells lottie-web the value is a frame number, not milliseconds. Multiplying scroll progress by totalFrames gives the frame to show, and fractional frames interpolate smoothly so the draw is continuous.
Yes. Click JSX for a React component, Vue for a Vue 3 SFC, Angular for a standalone component, or Tailwind for a React + Tailwind version. Call lottie.loadAnimation against a ref inside a mount effect, register the ScrollTrigger there, and on cleanup call anim.destroy() and kill the ScrollTrigger instance (or revert a gsap.context) so the SVG and scroll listener are released on unmount.