Migration GuideJune 2026 · 6 min read

Replace AOS and ScrollReveal.js
with one function call.

AOS and ScrollReveal.js work — but they require data attributes on every element, separate config files, and add another library to your bundle. scrollReveal from svg-scroll-draw does the same job in a single JS call with zero markup changes.

The problem with AOS and ScrollReveal.js

Both libraries work well for simple cases. But they have the same fundamental design: scroll animation is configured via HTML data attributesdata-aos="fade-up" or data-sr-id. This means:

  • Animations are scattered across your HTML — hard to trace, hard to change globally
  • No type safety — a typo in a data attribute silently does nothing
  • Data attributes mix concerns — presentation logic in HTML, not JavaScript
  • Both add ~10–20 KB on top of your bundle, just for fade-in effects
  • ScrollReveal.js is not actively maintained (last release: 2021)

scrollRevealfrom svg-scroll-draw is the JS-first alternative: all configuration lives in your JavaScript, it's fully typed, and it's part of a broader scroll animation platform — not a standalone one-trick library.

Zero-config: the default (fade up)

The most common scroll animation — fade up — requires zero configuration:

app.js
import { scrollReveal } from 'svg-scroll-draw/reveal';

// Fade up every .card on scroll — that's it
scrollReveal('.card');

Compare that with AOS:

index.html
<!-- AOS: every element needs a data attribute -->
<div class="card" data-aos="fade-up">…</div>
<div class="card" data-aos="fade-up" data-aos-delay="100">…</div>
<div class="card" data-aos="fade-up" data-aos-delay="200">…</div>

<!-- Plus AOS init in your JS -->
AOS.init({ duration: 800, once: true });

7 named presets

app.js
import { scrollReveal } from 'svg-scroll-draw/reveal';

scrollReveal('.card',    { preset: 'fadeUp'    }); // ↑ default
scrollReveal('.badge',   { preset: 'fadeDown'  }); // ↓
scrollReveal('.sidebar', { preset: 'fadeLeft'  }); // ←
scrollReveal('.tooltip', { preset: 'fadeRight' }); // →
scrollReveal('.hero',    { preset: 'scale'     }); // scale up from 88%
scrollReveal('.panel',   { preset: 'flip'      }); // rotateX 20→0 (3D flip)
scrollReveal('.tile',    { preset: 'flipX'     }); // rotateY 20→0

Custom from state

Full control — combine any properties with numeric values:

app.js
scrollReveal('.feature', {
  from: {
    opacity: 0,
    y:       40,     // translateY(40px) → 0
    scale:   0.95,   // scale(0.95) → 1
  },
  easing:  'ease-out',
  stagger: 0.08,
  once:    true,
});

// 3D card flip
scrollReveal('.card', {
  from: { opacity: 0, rotateX: 15, scale: 0.97 },
});

// Slide in from left with full opacity (no fade)
scrollReveal('.list-item', {
  from: { x: -48 },  // translateX(-48px) → 0, no opacity change
});

Stagger

The staggeroption offsets each element's trigger window — earlier elements in the list start animating sooner, later ones start a little further down the viewport. This creates a natural cascade without any per-element delay config:

app.js
// Cards cascade: card-0 animates first, then card-1, card-2…
scrollReveal('.pricing-card', {
  preset:  'fadeUp',
  stagger: 0.12,
  easing:  'ease-out',
});

// Very subtle stagger for a large list
scrollReveal('.testimonial', {
  preset:  'scale',
  stagger: 0.06,
});`}

React usage

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

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

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

Migration table

AOS / ScrollReveal.jsscrollReveal
data-aos="fade-up"scrollReveal('.el') // default
data-aos="fade-left"scrollReveal('.el', { preset: 'fadeLeft' })
data-aos="zoom-in"scrollReveal('.el', { preset: 'scale' })
data-aos="flip-up"scrollReveal('.el', { preset: 'flip' })
data-aos-delay="100"stagger: 0.1
data-aos-duration="800"trigger: { start: 'top 85%', end: 'top 50%' }
data-aos-once="true"once: true (default)
AOS.init({ once: true })scrollReveal('.el', { once: true })
AOS.refresh()instance.destroy(); scrollReveal(...)

Full API

index.js
import { scrollReveal } from 'svg-scroll-draw/reveal';

const instance = scrollReveal(
  '.card',            // CSS selector, NodeList, or Element[]
  {
    preset:  'fadeUp',   // 'fadeUp' | 'fadeDown' | 'fadeLeft' | 'fadeRight'
                         // | 'scale' | 'flip' | 'flipX'
    from: {              // custom start state (overrides preset)
      opacity: 0,
      x: 0, y: 0,
      scale: 1,
      rotate: 0,
      rotateX: 0, rotateY: 0,
    },
    stagger: 0.08,       // viewport-% offset per element (default: 0.08)
    easing:  'ease-out', // any easing name or custom function
    once:    true,       // freeze at max progress (default: true)
    trigger: {           // override default trigger window
      start: 'top 88%',
      end:   'top 53%',
    },
    onEnter: () => {},   // fires when first element enters view
    onLeave: () => {},   // fires when last element leaves view
  }
);

instance.destroy(); // removes all animations, restores original styles
scrollReveal vs scrollAnimateGroup: Use scrollReveal for the common case — preset animations on a group of elements. Use scrollAnimateGroup when you need precise per-element control over different CSS properties with shared trigger timing.