npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

svg-scroll-draw

v2.9.0

Published

Scroll-driven SVG path drawing animation library — zero dependencies, ~4.4 KB gzipped, works with React, Vue, and vanilla JS

Readme

svg-scroll-draw

CI npm downloads bundle size license GitHub stars

The scroll animation platform. Animate SVG paths, CSS properties, counters, video, and text — all scroll-driven, all MIT, all under 10 KB. GSAP is overkill.

Live Demo · npm · Docs · Examples · Report a bug


What's in v2

| API | Import | What it does | |---|---|---| | scrollAnimate | svg-scroll-draw | Animate any CSS property on any element driven by scroll. Replaces gsap.to + ScrollTrigger. | | scrollCounter | svg-scroll-draw | Animate a number from from to to as it scrolls into view. Custom format, decimals, easing. | | scrollParallax | svg-scroll-draw | Move any element at a different rate than scroll. Speed multiplier, reverse direction. | | scrollDraw | svg-scroll-draw | The original — scroll-driven SVG path drawing via stroke-dashoffset. | | scrollVideo | svg-scroll-draw/video | Tie <video>.currentTime to scroll. The Apple/Stripe product-page pattern. | | scrollText | svg-scroll-draw/text | Split text into chars/words/lines and stagger-animate each on scroll. Free GSAP SplitText. | | devtools | svg-scroll-draw/devtools | Visual overlay showing every active animation's triggers and progress. Dev-only. |

All APIs return the same instance object: { destroy, replay, pause, resume, seek, getProgress }.


Install

npm i svg-scroll-draw
# pnpm add svg-scroll-draw
# yarn add svg-scroll-draw
# bun add svg-scroll-draw

scrollAnimate — any CSS property on scroll

import { scrollAnimate } from 'svg-scroll-draw';

// Fade + slide in
scrollAnimate('#hero-text', {
  props: {
    opacity:   [0, 1],
    transform: ['translateY(40px)', 'translateY(0px)'],
  },
  easing: 'ease-out',
  once:   true,
});

// Color transition
scrollAnimate('#section', {
  props: {
    backgroundColor: ['#ffffff', '#0d0d0d'],
    color:           ['#000000', '#ffffff'],
  },
  trigger: { start: 'top 60%', end: 'top 20%' },
});

// Multiple elements with stagger
document.querySelectorAll('.card').forEach((el, i) => {
  scrollAnimate(el, {
    props: { opacity: [0, 1], transform: ['translateY(32px)', 'translateY(0)'] },
    trigger: { start: `top ${85 - i * 5}%`, end: `top ${50 - i * 5}%` },
    easing: 'ease-out',
    once:   true,
  });
});

React

import { ScrollAnimate } from 'svg-scroll-draw/react';

<ScrollAnimate
  props={{ opacity: [0, 1], transform: ['translateY(24px)', 'translateY(0)'] }}
  easing="ease-out"
  once
>
  <div>Any content — HTML or SVG</div>
</ScrollAnimate>

scrollAnimate options

| Option | Type | Default | Description | |---|---|---|---| | props | Record<string, [from, to]> | — | CSS properties to animate. Supports numbers, hex/rgb colors, transform functions, and unit values like 40px. | | trigger.start | string | "top bottom" | When animation begins | | trigger.end | string | "bottom top" | When animation ends | | easing | string \| fn | "ease-out" | Same easing system as scrollDraw | | speed | number | 1 | Animation scale factor | | once | boolean | false | Freeze at max progress — doesn't reverse on scroll back | | axis | "x" \| "y" | "y" | Scroll axis | | native | boolean | true | Use CSS animation-timeline: view() fast path when eligible | | onProgress | (n: number) => void | — | Called every frame with alpha 0–1 | | onComplete | () => void | — | Fires when alpha reaches 1 |


scrollCounter — animated number on scroll

import { scrollCounter } from 'svg-scroll-draw';

// Simple count-up
scrollCounter('#users', { to: 50_000, once: true });

// Formatted currency
scrollCounter('#revenue', {
  to:     1_250_000,
  format: n => '$' + Math.round(n).toLocaleString(),
  easing: 'ease-out',
  once:   true,
});

// Percentage with decimal
scrollCounter('#rate', {
  from:     0,
  to:       94.7,
  decimals: 1,
  format:   n => n.toFixed(1) + '%',
});

React

import { ScrollCounter } from 'svg-scroll-draw/react';

<ScrollCounter to={50000} format={n => n.toLocaleString()} once />

scrollCounter options

| Option | Type | Default | Description | |---|---|---|---| | to | number | — | Target value (required) | | from | number | 0 | Starting value. Set higher than to to count down. | | format | (n: number) => string | String(Math.round(n)) | Custom formatter | | decimals | number | — | Shorthand for format: n => n.toFixed(decimals) | | easing | string \| fn | "ease-out" | — | | trigger | TriggerConfig | { start: 'top 80%', end: 'top 20%' } | — | | once | boolean | true | — | | onComplete | () => void | — | — |


scrollParallax — parallax in one line

import { scrollParallax } from 'svg-scroll-draw';

scrollParallax('#hero-bg', { speed: 0.4 });           // 40% of scroll rate
scrollParallax('#floating-badge', { speed: -0.3 });   // opposite direction
scrollParallax('#sidebar', { speed: 0.3, axis: 'x' }); // horizontal

speed is a multiplier relative to element height. 0.5 = half scroll speed, -0.2 = opposite direction.


scrollVideo — video scrubbing on scroll

import { scrollVideo } from 'svg-scroll-draw/video';

// Scrub the full video as user scrolls through the section
scrollVideo('#hero-video', {
  trigger: { start: 'top top', end: 'bottom top' },
});

// Scrub only the first 3 seconds
scrollVideo('#product-reveal', {
  from:    0,
  to:      3,
  trigger: { start: 'top 80%', end: 'top 20%' },
  easing:  'ease-in-out',
});
<!-- Recommended attributes for smooth scrubbing -->
<video id="hero-video" src="hero.mp4" muted playsinline preload="auto"></video>

React

import { ScrollVideo } from 'svg-scroll-draw/react';

<ScrollVideo src="/hero.mp4" trigger={{ start: 'top top', end: 'bottom top' }} />

scrollVideo options

| Option | Type | Default | Description | |---|---|---|---| | from | number | 0 | Start time in seconds | | to | number | video.duration | End time in seconds | | trigger | TriggerConfig | { start: 'top top', end: 'bottom top' } | — | | easing | string \| fn | "linear" | — | | once | boolean | false | — | | preload | "auto" \| "metadata" | "auto" | Sets preload attribute if not already present | | onReady | () => void | — | Fires when video metadata is loaded | | onProgress | (n: number) => void | — | — | | onComplete | () => void | — | — |


scrollText — text reveal on scroll

Free replacement for GSAP SplitText (which requires a $150+/yr Club GreenSock subscription).

import { scrollText } from 'svg-scroll-draw/text';

// Words fade up one by one
scrollText('#headline', {
  split:   'words',
  stagger: 0.05,
  from:    { opacity: 0, y: 24 },
  easing:  'ease-out',
  once:    true,
});

// Characters with rotation
scrollText('#tagline', {
  split:   'chars',
  stagger: 0.025,
  from:    { opacity: 0, y: 32, rotate: 8 },
  once:    true,
});

React

import { ScrollText } from 'svg-scroll-draw/react';

<ScrollText split="words" stagger={0.05} from={{ opacity: 0, y: 24 }} once>
  Animate this headline word by word.
</ScrollText>

scrollText options

| Option | Type | Default | Description | |---|---|---|---| | split | "chars" \| "words" \| "lines" | "words" | How to split the text | | stagger | number | 0.04 | Delay between each unit (0–1 fraction of the animation range) | | from | { opacity?, y?, x?, rotate?, scale? } | { opacity: 0, y: 24 } | Starting state of each unit | | easing | string \| fn | "ease-out" | — | | trigger | TriggerConfig | { start: 'top 85%', end: 'top 40%' } | — | | once | boolean | true | — | | onComplete | () => void | — | — |

Accessibility: scrollText preserves the original text in aria-label on the container and adds aria-hidden="true" to all split spans. destroy() fully restores the original HTML.


scrollDraw — SVG path drawing (v1 API, unchanged)

import { scrollDraw } from 'svg-scroll-draw';

scrollDraw('#my-svg', {
  easing:    'ease-out',
  speed:     1.2,
  fade:      true,
  once:      true,
  stagger:   0.15,
  trigger:   { start: 'top 80%', end: 'top 20%' },
});

React / Next.js

import { ScrollDraw } from 'svg-scroll-draw/react';

export default function Hero() {
  return (
    <ScrollDraw speed={1.2} fade easing="ease-out" once>
      <svg width="500" height="500" viewBox="0 0 500 500">
        <path d="M10 80 C 40 10, 60 10, 95 80" stroke="black" fill="none" />
      </svg>
    </ScrollDraw>
  );
}

Next.js App Router: add 'use client' to any component that uses these wrappers.

Vue 3

<script setup>
import { ScrollDraw } from 'svg-scroll-draw/vue';
</script>

<template>
  <ScrollDraw easing="ease-out" :speed="1.2">
    <svg>...</svg>
  </ScrollDraw>
</template>

scrollDraw options

| Option | Type | Default | Description | |---|---|---|---| | selector | string | "path, polyline…" | CSS selector for child elements to animate | | speed | number | 1 | Scale factor relative to scroll distance | | fade | boolean | false | Animate opacity 0 → 1 simultaneously | | easing | string \| fn | "linear" | linear, ease-in, ease-out, ease-in-out, spring, bounce, elastic, or (t) => t | | stagger | number | 0 | Offset between each path starting (fraction of scroll range) | | direction | "forward" \| "reverse" | "forward" | reverse erases the path as you scroll | | trigger.start | string | "top bottom" | When animation begins | | trigger.end | string | "bottom top" | When animation ends | | once | boolean | false | Lock at max progress | | debug | boolean | false | Show trigger zone overlay (dev only) | | axis | "x" \| "y" | "y" | Scroll axis | | scrollContainer | string \| Element | window | Custom scroll container | | autoReverse | boolean | false | Reverse when scrolling back up | | delay | number | 0 | ms before the engine starts observing | | strokeColor | string \| [string, string] | — | Static color or [from, to] animation | | strokeWidth | number \| [number, number] | — | Static width or [from, to] animation | | fillOpacity | number \| [number, number] | — | Animate fill opacity in sync with the draw | | clip | boolean \| "left" \| "right" \| "top" \| "bottom" \| "center" | false | Reveal via CSS clip-path instead of stroke-dashoffset | | morphTo | string | — | SVG path d value to morph toward | | waypoints | Record<number, () => void> | — | Callbacks at specific progress thresholds (0–1) | | velocityScale | boolean \| number | false | Scale draw speed by scroll velocity | | repeat | number \| "infinite" | 0 | Replay N times after completion | | repeatDelay | number | 0 | ms between repeats | | autoplay | boolean | false | Trigger on viewport entry instead of scroll | | duration | number | 1000 | Duration in ms for autoplay mode | | native | boolean | true | Use CSS animation-timeline: view() when eligible | | preset | "sketch" \| "reveal" \| "typewriter" \| "cinematic" \| "spring" | — | Apply a named option preset as the base config | | onProgress | (alpha: number) => void | — | Called every frame with draw progress (0–1) | | onStart | () => void | — | Fires on the first frame | | onComplete | () => void | — | Fires when all paths reach 100% |


Instance API

Every function returns the same instance object:

const inst = scrollAnimate('#el', { props: { opacity: [0, 1] } });
// Same for scrollDraw, scrollCounter, scrollVideo, scrollText, scrollParallax

inst.pause();           // pause at current frame
inst.resume();          // resume from paused state
inst.seek(0.5);         // jump to 50% and pause
inst.replay();          // reset and replay from the beginning
inst.getProgress();     // → number 0–1
inst.destroy();         // remove all listeners and clean up

Easing

All APIs share the same easing system:

// Named strings
easing: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'spring' | 'bounce' | 'elastic'

// Custom function
easing: (t: number) => number

// Factory functions for parameterized physics easings
import { createSpring, createBounce, createElastic } from 'svg-scroll-draw';

easing: createSpring({ tension: 3, friction: 1.8 })
easing: createBounce({ bounces: 4, decay: 0.4 })
easing: createElastic({ amplitude: 1.5, period: 0.3 })

Presets

Named option bags for scrollDraw:

import { scrollDraw, PRESETS } from 'svg-scroll-draw';

scrollDraw('#logo', { preset: 'sketch' });    // staggered ease-in, pencil feel
scrollDraw('#hero', { preset: 'reveal' });    // fade + ease-out, once
scrollDraw('#type', { preset: 'typewriter' }); // fast linear stagger
scrollDraw('#film', { preset: 'cinematic' }); // slow ease-in-out + fade
scrollDraw('#icon', { preset: 'spring' });    // spring easing

// Inspect or extend
console.log(PRESETS.sketch);

DevTools

Visual debug overlay — shows every active animation's trigger window, progress bar, and type. Zero production bytes.

import { devtools } from 'svg-scroll-draw/devtools';

devtools.enable();    // Cmd/Ctrl+Shift+S to toggle
devtools.disable();
devtools.highlight('#my-element'); // outline for 2 seconds

Sub-path exports

| Import | Contents | |---|---| | svg-scroll-draw | scrollDraw, scrollAnimate, scrollCounter, scrollParallax, PRESETS, easing factories | | svg-scroll-draw/react | ScrollDraw, ScrollAnimate, ScrollCounter, ScrollVideo, ScrollText, useScrollDrawProgress | | svg-scroll-draw/vue | ScrollDraw, useScrollDraw · v2: ScrollAnimate, ScrollCounter, ScrollVideo, ScrollText, useScrollAnimate, useScrollCounter, useScrollVideo, useScrollText | | svg-scroll-draw/svelte | scrollDraw action, createScrollDraw · v2: scrollAnimate, scrollCounterAction, scrollVideoAction, scrollTextAction, createScrollAnimate, createScrollCounter, createScrollVideo, createScrollText | | svg-scroll-draw/solid | useScrollDraw, createScrollDraw · v2: useScrollAnimate, useScrollCounter, useScrollVideo, useScrollText, createScrollAnimate, createScrollCounter, createScrollVideo, createScrollText | | svg-scroll-draw/angular | ScrollDrawRef · v2: ScrollAnimateRef, ScrollCounterRef, ScrollVideoRef, ScrollTextRef | | svg-scroll-draw/astro | initScrollDraw() · v2: initScrollAnimate(), initScrollCounter(), initScrollText(), initAll() | | svg-scroll-draw/nuxt | All Vue v1+v2 composables and components, createScrollDrawPlugin() | | svg-scroll-draw/video | scrollVideo | | svg-scroll-draw/text | scrollText | | svg-scroll-draw/group | scrollDrawGroup, scrollDrawSequence · v2: scrollAnimateGroup, scrollAnimateSequence, scrollParallaxGroup | | svg-scroll-draw/timeline | scrollDrawTimeline | | svg-scroll-draw/cinematic | Cinematic | | svg-scroll-draw/devtools | devtools (dev-only) |


Bundle sizes

| Entry | Gzipped | |---|---| | svg-scroll-draw (main) | ~9 KB | | svg-scroll-draw/react | ~3 KB | | svg-scroll-draw/video | ~1.5 KB | | svg-scroll-draw/text | ~2 KB | | svg-scroll-draw/devtools | ~4 KB (dev only) |

GSAP core + ScrollTrigger = ~40 KB gzipped. svg-scroll-draw v2 covers 95% of those use cases at ~9 KB.


CDN / Web Component

<script src="https://unpkg.com/svg-scroll-draw/dist/cdn/svg-scroll-draw.global.js"></script>

<scroll-draw easing="ease-out" speed="1.2">
  <svg>...</svg>
</scroll-draw>

<script>
  SvgScrollDraw.scrollDraw('#container', { easing: 'ease-out' });
  SvgScrollDraw.scrollAnimate('#hero', {
    props: { opacity: [0, 1] }, easing: 'ease-out', once: true,
  });
</script>

CLI

npx svg-scroll-draw init

Scaffolds a ready-to-use starter file for React, Vue, Svelte, Solid, or Vanilla JS.


Browser support

Chrome 80+, Safari 14+, Firefox 75+, Edge 80+

Native CSS animation-timeline: view() fast path: Chrome 115+, Safari 18+, Firefox 110+. Automatically falls back to the JS engine on older browsers.


License

MIT