ANIMATE SVG
PATHS AS YOU SCROLL.
The definitive modern library. ~3 KB gzipped. Works in React, Next.js, and vanilla JS.
Live on this page
This page
draws itself.
The fountain pen illustration to the right isn't a video or a GIF. It's a live SVG animated by svg-scroll-draw — the exact same package you'd install. Scroll down and watch the ink ribbon draw out in real time.
<ScrollDraw
selector=".ink-ribbon"
easing="ease-out"
speed={1.5}
>
<svg>{/* fountain pen */}</svg>
</ScrollDraw>The problem
Every existing tool
is broken.
Overkill for a single effect — and requires a Club GreenSock subscription for commercial use.
Locks you into one ecosystem and adds 35KB of runtime overhead just to draw a line.
Requires manually targeting individual path IDs. Crashes in Next.js with window is not defined.
Bundle size
~3 KB.
Not 40 KB.
Sizes are minified + gzipped. GSAP DrawSVG requires a paid Club GreenSock license for commercial use.
Zero config
Drop in.
It just works.
Wrap any SVG with <ScrollDraw>. The engine discovers every path, measures total length, and animates stroke-dashoffset as it enters the viewport. No configuration required.
import { ScrollDraw } from 'svg-scroll-draw/react';
export default function Hero() {
return (
<ScrollDraw>
<svg>...</svg>
</ScrollDraw>
);
}Easing
Speed — 1.0×
Easing
Speed — 1.5×
Easing + speed
Natural motion,
your way.
Five built-in curves — including a spring that overshoots and settles — or any custom (t: number) => number function. The speed prop compresses or stretches the draw relative to your scroll distance.
<ScrollDraw
easing="spring"
speed={1.5}
>
<svg>...</svg>
</ScrollDraw>Fade
Draw and
materialise.
Enable fade to simultaneously animate opacity: 0 → 1 as the path draws. Lines seem to emerge from nothing — elegant for technical illustrations and hero graphics.
<ScrollDraw
fade={true}
easing="ease-in-out"
>
<svg>...</svg>
</ScrollDraw>Easing
Speed — 1.0×
Easing
Speed — 0.9×
Auto-discovery
Every path.
Automatically.
Every <path>, <line>, <polyline>, and <polygon> inside the container is discovered, measured, and animated.
No manual selectors. No brittle ID targeting. When your designer updates the SVG, your code doesn't break.
Callback
Know when
it's done.
The onComplete callback fires the moment the path reaches 100%. Use it to chain animations, reveal UI, or fire analytics. Hit Replay below to see it fire again.
<ScrollDraw
onComplete={() => {
console.log('drawn!');
}}
>
<svg>...</svg>
</ScrollDraw>Easing
Speed — 1.0×
Easing
Speed — 0.9×
Auto Reverse
Scroll up,
draw back.
Enable autoReverse and the animation automatically follows scroll direction — drawing forward as you scroll down, erasing as you scroll back up. No manual direction switching needed.
<ScrollDraw autoReverse> <svg>...</svg> </ScrollDraw>
Color Animation
Color that
follows the draw.
Pass a [from, to] tuple to strokeColor and the stroke interpolates between two colors as the path draws. No extra CSS or keyframes — the engine handles it per-frame.
<ScrollDraw
strokeColor={['#ff6b9d', '#ffc900']}
easing="ease-out"
>
<svg>...</svg>
</ScrollDraw>Easing
Speed — 0.8×
Easing
Speed — 0.9×
Width Animation
Hairline thin
to bold.
Pass a [from, to] tuple to strokeWidth and the line grows from a hairline to any thickness as it draws. Combine with strokeColor for dramatic logo reveals.
<ScrollDraw
strokeWidth={[0.5, 6]}
easing="ease-in-out"
>
<svg>...</svg>
</ScrollDraw>Waypoints
Fire callbacks
mid-draw.
waypoints lets you fire callbacks at any progress threshold — reveal UI at 50%, trigger confetti at 100%, or sync multiple animations precisely. They reset on replay().
<ScrollDraw
waypoints={{
0.25: () => revealLabel(),
0.5: () => animateChart(),
0.75: () => showCTA(),
1: () => fireConfetti(),
}}
>
<svg>...</svg>
</ScrollDraw>Easing
Speed — 0.8×
Path Morphing
Shape-shifts
as you scroll.
Pass a morphTo path string and the SVG shape interpolates from its original d to the target as you scroll. Both paths must have the same command count — perfect for logo reveals and icon transitions.
<ScrollDraw
morphTo="M 130 40 L 220 130 L 130 220 L 40 130 Z"
easing="ease-in-out"
>
<svg>
<path d="M 130 40 C 220 40 220 220 130 220
C 40 220 40 40 130 40 Z" />
</svg>
</ScrollDraw>Easing
Speed — 0.9×
Group API
Many SVGs,
one command.
Use scrollDrawGroup to animate multiple SVG containers simultaneously with shared options. Or use scrollDrawSequence to chain them — each one starts only after the previous finishes.
import { scrollDrawGroup, scrollDrawSequence }
from 'svg-scroll-draw/group';
// Animate all three at once
const group = scrollDrawGroup(
['#chart-1', '#chart-2', '#chart-3'],
{ easing: 'ease-out', stagger: 0.1 }
);
// Or chain them in sequence
const seq = scrollDrawSequence(
['#step-1', '#step-2', '#step-3'],
{ easing: 'spring' }
);useScrollDrawProgress
Progress as a
React value.
useScrollDrawProgress exposes scroll progress (0–1) as a plain React number — no SVG required. Drive any animation: colors, counters, layouts, spring physics, or canvas. Same trigger/speed/easing API as ScrollDraw.
import { useScrollDrawProgress }
from 'svg-scroll-draw/react';
const ref = useRef(null);
const progress = useScrollDrawProgress(ref, {
trigger: { start: 'top 80%',
end: 'bottom 20%' },
easing: 'ease-out',
});
// progress is 0 → 1 as you scroll
// drive anything with it ↓
const opacity = progress;
const scale = 0.8 + progress * 0.2;
const color = lerp('#ccc', '#ff90e8', progress);Fill Opacity
Outline draws.
Fill floods in.
Pass fillOpacity={[0, 1]} and the element's fill animates from fully transparent to fully opaque in sync with the stroke draw. A single number sets a static override. No React state, no onComplete hack needed.
<ScrollDraw
easing="ease-in-out"
speed={0.9}
fillOpacity={[0, 1]}
strokeColor={['#d1d5dc', '#ff90e8']}
once
>
<svg>
{/* fill is set on the element — */}
{/* fillOpacity animates 0 → 1 */}
<path fill="#ff90e8" stroke="#ff90e8" d="…" />
</svg>
</ScrollDraw>Clip Mode
Reveal anything.
Not just SVGs.
Set clip to skip stroke-dashoffset entirely and use CSS clip-path instead. Works on images, cards, text blocks, gradients — any HTML or SVG content.
Five directions: left, right, top, bottom, center. All existing options — easing, speed, trigger, once — work as normal.
// Reveal any content — not just SVG paths
<ScrollDraw clip="left" easing="ease-out" once>
<img src="/hero.png" />
</ScrollDraw>
<ScrollDraw clip="center" easing="spring" speed={1.2}>
<div className="card">
<h2>Radial reveal</h2>
<p>Works on any HTML content.</p>
</div>
</ScrollDraw>
// true defaults to 'left'
<ScrollDraw clip easing="ease-in-out">
<YourComponent />
</ScrollDraw>Reference
All options.
selectortype:stringdefault:"path, polyline…"CSS selector to target specific child elements.
speedtype:numberdefault:1Scale factor — values above 1 complete the animation faster.
fadetype:booleandefault:falseAnimate opacity 0 → 1 simultaneously while drawing.
easingtype:string | fndefault:"linear"linear · ease-in · ease-out · ease-in-out · spring · or custom (t) => t.
staggertype:numberdefault:0Normalized scroll-progress offset between each path starting. e.g. 0.15 → each path begins 15% of the range after the previous.
directiontype:"forward"|"reverse"default:"forward"forward draws the path in. reverse starts fully drawn and erases as you scroll.
oncetype:booleandefault:falseDraw once and stay drawn — animation does not reverse when scrolling back up.
debugtype:booleandefault:falseRenders a visual overlay showing trigger start/end zones. Dev-only, stripped in production.
axistype:"x" | "y"default:"y"Scroll axis to track. Use "x" for horizontal scroll containers.
scrollContainertype:string | Elementdefault:windowCSS selector or Element for a custom scroll container instead of the window.
autoReversetype:booleandefault:falseAutomatically reverse the animation when the user scrolls back up.
delaytype:numberdefault:0Milliseconds to wait before the engine starts observing — useful for page-load sequences.
strokeColortype:string | [string,string]default:—Static color override or [from, to] tuple to animate stroke color as the path draws.
strokeWidthtype:number | [number,number]default:—Static width override or [from, to] tuple to animate stroke width as the path draws.
fillOpacitytype:number | [number,number]default:—Static fill-opacity override or [from, to] tuple to animate fill opacity in sync with the stroke draw. Use [0, 1] to flood fill as the outline traces itself.
cliptype:boolean | stringdefault:falseReveal using CSS clip-path instead of stroke-dashoffset. Works on any HTML/SVG content. Values: 'left' (default), 'right', 'top', 'bottom', 'center' (radial). All easing/speed/trigger options apply.
waypointstype:Record<number, fn>default:—Fire callbacks at specific progress thresholds (0–1). e.g. { 0.5: () => doSomething() }. Resets on replay().
trigger.starttype:stringdefault:"top bottom"When animation begins. Accepts anchor strings ("top bottom") or viewport percentages ("20%").
trigger.endtype:stringdefault:"bottom top"When animation ends. Accepts anchor strings or viewport percentages.
velocityScaletype:boolean | numberdefault:falseScale draw speed by scroll velocity — faster scrolling draws faster. Pass a number to set sensitivity.
thresholdtype:numberdefault:0IntersectionObserver threshold — how much of the element must be visible before animating.
rootMargintype:stringdefault:"0px"IntersectionObserver rootMargin — expand or shrink the trigger zone.
repeattype:number | "infinite"default:0Repeat the animation N times. Use "infinite" to loop forever.
repeatDelaytype:numberdefault:0Milliseconds to wait between repeats.
morphTotype:stringdefault:—SVG path `d` value to morph toward as the animation progresses.
onStarttype:() => voiddefault:—Fires once on the first frame the animation begins drawing.
onProgresstype:(n: number) => voiddefault:—Called every animation frame with current draw alpha (0–1).
onCompletetype:() => voiddefault:—Fires once when all paths reach 100% draw progress.
useScrollDrawProgresstype:hookdefault:—React hook — returns scroll progress (0–1) for any element. Same trigger/speed/easing options as ScrollDraw. No SVG required.
Quickstart
Works everywhere
you do.
import { ScrollDraw } from 'svg-scroll-draw/react';
export default function Hero() {
return (
<ScrollDraw easing="ease-out" speed={1.2}>
<svg>...</svg>
</ScrollDraw>
);
}