ReactWorks with Next.js App Router, Remix, Vite

React scroll animations.
9 KB. Zero deps.

Typed React components and hooks for every scroll animation pattern — fade, slide, pin, snap, counter, text reveal, video scrub, horizontal sections. No GSAP. No Framer Motion. No config files.

$npm i svg-scroll-draw
API Reference →vs GSAP →

Quick start.

1. Reveal on scroll (the common case)

FeaturesSection.tsx
'use client';
import { useEffect } from 'react';
import { scrollReveal } from 'svg-scroll-draw/reveal';

export function FeaturesSection() {
  useEffect(() => {
    const inst = scrollReveal('.feature-card', {
      preset:  'fadeUp',
      stagger: 0.1,
      once:    true,
    });
    return () => inst.destroy();
  }, []);

  return (
    <section>
      {features.map(f => (
        <div key={f.id} className="feature-card">{f.content}</div>
      ))}
    </section>
  );
}

2. Animate any CSS property

HeroSection.tsx
'use client';
import { useEffect, useRef } from 'react';
import { scrollAnimate } from 'svg-scroll-draw';
// Or use the component wrapper:
import { ScrollAnimate } from 'svg-scroll-draw/react';

// Hook style (imperative)
export function HeroSection() {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!ref.current) return;
    const inst = scrollAnimate(ref.current, {
      props: {
        opacity:   [0, 1],
        transform: ['translateY(40px)', 'translateY(0)'],
      },
      easing: 'ease-out',
      once:   true,
    });
    return () => inst.destroy();
  }, []);

  return <div ref={ref}>Hero content</div>;
}

// Component style (declarative)
export function HeroDeclarative() {
  return (
    <ScrollAnimate
      props={{ opacity: [0, 1], transform: ['translateY(24px)', 'translateY(0)'] }}
      easing="ease-out"
      once
    >
      <div>Hero content</div>
    </ScrollAnimate>
  );
}

3. Sticky pin section

ProductSection.tsx
'use client';
import { useEffect, useRef } from 'react';
import { scrollPin } from 'svg-scroll-draw/pin';

export function ProductSection() {
  const imageRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!imageRef.current) return;
    const inst = scrollPin(imageRef.current, {
      top:         80,                     // below fixed header
      pinDistance: window.innerHeight * 3,
      onEnter:     () => setActive(true),
      onLeave:     () => setActive(false),
    });
    return () => inst.destroy();
  }, []);

  return (
    <div style={{ display: 'flex', gap: 64 }}>
      <div ref={imageRef} style={{ width: '50%' }}>
        <img src="/product.webp" alt="Product" />
      </div>
      <div style={{ flex: 1 }}>
        {features.map(f => <FeatureBlock key={f.id} {...f} />)}
      </div>
    </div>
  );
}

4. Animated counter

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

export function StatsSection() {
  return (
    <div className="stats-grid">
      <ScrollCounter
        to={50000}
        format={n => Math.round(n).toLocaleString() + '+'}
        easing="ease-out"
        once
      />
      <ScrollCounter to={9} format={n => '~' + Math.round(n) + ' KB'} once />
    </div>
  );
}

5. Text reveal (replaces GSAP SplitText)

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

export function Headline() {
  return (
    <ScrollText
      split="words"
      stagger={0.07}
      from={{ opacity: 0, y: 32 }}
      easing="ease-out"
      once
    >
      <h1>Animate everything on scroll.</h1>
    </ScrollText>
  );
}

Every React API.

scrollReveal
svg-scroll-draw/reveal

Reveal elements on scroll. 7 presets, stagger, custom from state. Replaces AOS.

ScrollAnimate
svg-scroll-draw/react

Animate any CSS property driven by scroll. Component wrapper around scrollAnimate.

useScrollDrawhook
svg-scroll-draw/react

Hook to attach scroll-driven SVG path animation to a ref.

ScrollText
svg-scroll-draw/react

Split text + stagger reveal. words, chars, or lines. Replaces GSAP SplitText.

ScrollCounter
svg-scroll-draw/react

Animated number counter driven by scroll. Format function, decimals, easing.

scrollPin
svg-scroll-draw/pin

Pin an element at a viewport position while content scrolls past it. Full lifecycle callbacks.

scrollSnap
svg-scroll-draw/snap

Section snapping with custom easing. snapTo(), getCurrentIndex(), onSnap.

scrollProgress
svg-scroll-draw/progress

Expose scroll progress as --scroll-progress CSS custom property. Drive CSS with calc().

scrollHorizontal
svg-scroll-draw/horizontal

Apple-style horizontal scroll sections driven by vertical scroll.

Next.js App Router.

svg-scroll-draw is fully SSR-safe. All engines guard against window being undefined. In Next.js App Router, add 'use client' to any component that calls scroll APIs — or use dynamic(() => import(...), { ssr: false } ) to lazy-load entire animated sections.

app/page.tsx
import dynamic from 'next/dynamic';

// Option A: 'use client' component
// AnimatedHero.tsx starts with 'use client'
import { AnimatedHero } from '@/components/AnimatedHero';

// Option B: dynamic import with ssr: false
const AnimatedSection = dynamic(
  () => import('@/components/AnimatedSection'),
  { ssr: false }
);

export default function Page() {
  return (
    <>
      <AnimatedHero />
      <AnimatedSection />
    </>
  );
}

Drop-in for React + Next.js.

MIT. Zero deps. TypeScript. 423 tests. Works everywhere React works.