@theo-js/react-gsap-reveal
v2.0.1
Published
A lightweight, type-safe React library for scroll-triggered GSAP animations — built for modern React (App Router, RSC, SSR-safe).
Maintainers
Readme
@theo-js/react-gsap-reveal
A lightweight, type-safe React library for scroll-triggered GSAP animations — built for modern React (App Router, RSC, SSR-safe).
Demo
✨ Features
- ✅ Scroll-based reveal animations powered by
IntersectionObserver - ✅ Built-in animation presets
- ✅ Custom animation support via
createRevealSystem - ✅ Fully type-safe animation names (default + custom inferred)
- ✅ Optional defaults provider (animation, repeat, GSAP options)
- ✅ Stagger support
- ✅ Repeat on re-entry
- ✅ Polymorphic
asprop for semantic markup - ✅ SSR-safe (Next.js compatible)
- ✅ Singleton
IntersectionObserverfor optimal performance
📦 Installation
npm install @theo-js/react-gsap-reveal gsapor
pnpm add @theo-js/react-gsap-reveal gsap🚀 Quick Start
1️⃣ (Optional) Configure the IntersectionObserver
By default, the library creates a singleton IntersectionObserver with sensible defaults.
You only need to use RevealObserverSetup if you want to override the default observer options.
import { RevealObserverSetup } from "@theo-js/react-gsap-reveal";
export default function RootLayout({ children }) {
return (
<html>
<body>
<RevealObserverSetup threshold={1} rootMargin="0px" />
{children}
</body>
</html>
);
}⚠️ Important
- The observer is a singleton, created once on first use.
- Changing props of
RevealObserverSetupafter it has mounted has no effect. - Mounting
RevealObserverSetupafter a<Reveal />has already been rendered has no effect. - The observer configuration must be defined before the first Reveal mounts.
This design ensures maximum performance and avoids multiple observers running simultaneously.
If you don’t need custom observer options, you can safely omit this component.
2️⃣ Use the default Reveal component
"use client";
import { createRevealSystem } from "@theo-js/react-gsap-reveal";
export const { Reveal } = createRevealSystem();
export default function Example() {
return (
<Reveal>
<div>Hello world</div>
</Reveal>
);
}🎬 Built-in Animations
By default, the library includes:
fadeUpfadeInslideLeftslideRightscaleIn
Usage:
<Reveal animation="slideLeft">
<div>Slide from left</div>
</Reveal>All animation names are fully type-safe.
⚙️ Props API
<Reveal />
| Prop | Type | Default | Description |
| ------------ | ----------------------------------------------- | ---------- | ------------------------------------------------------------- |
| animation | AnimationName | "fadeUp" | Animation preset to use |
| repeat | boolean | false | Re-run animation when re-entering viewport |
| options | GSAPTweenVars | — | GSAP animation options (duration, delay, ease, stagger, etc.) |
| as | ElementType | "span" | Wrapper element |
| childAs | ElementType | "span" | Child wrapper element |
| childProps | ComponentProps or (index) => ComponentProps | — | Props applied to each child |
🎞 Stagger Example
<Reveal options={{ stagger: 0.3 }} as="ul" childAs="li">
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</Reveal>🔁 Repeat Animation
<Reveal repeat>
<div>Repeat me</div>
</Reveal>The reveal will be retriggered everytime it enters the viewport.
⚠️ Important
When using repeat with custom animations, make sure to reset the revealed element by implementing the onLeave property in your animation definition.
🧩 Custom Animations (Factory API)
To add custom animations, use createRevealSystem.
"use client";
import { createRevealSystem } from "@theo-js/react-gsap-reveal";
import gsap from "gsap";
export const { Reveal: CustomReveal, RevealDefaultsProvider } =
createRevealSystem({
customAnimations: {
rotateIn: {
fromStyles: {
opacity: 0,
transform: "rotate(-90deg) scale(0.8)",
},
onEnter: ({ elements, options }) => {
gsap.to(elements, {
opacity: 1,
scale: 1,
rotation: 0,
...options,
});
},
onLeave: ({ elements, options }) => {
gsap.to(elements, {
opacity: 0,
scale: 0.8,
rotation: -90,
...options,
});
},
},
},
});Now fully typed:
<CustomReveal animation="rotateIn" />TypeScript automatically includes:
"fadeUp" | "fadeIn" | "slideLeft" | "slideRight" | "scaleIn" | "rotateIn"No .d.ts augmentation required.
🎛 Global Defaults (Optional Provider)
You can define default animation settings via RevealDefaultsProvider.
<RevealDefaultsProvider animation="rotateIn" options={{ duration: 2 }}>
<CustomReveal>
<div>Uses rotateIn (2s)</div>
</CustomReveal>
<CustomReveal options={{ duration: 6 }}>
<div>Overrides to 6s</div>
</CustomReveal>
</RevealDefaultsProvider>Priority order
- Instance props
- Defaults provider
- Library defaults
🧠 How It Works
- A single
IntersectionObserverinstance tracks all reveal elements. - This avoids unnecessary observers and improves performance.
- Initial styles (
fromStyles) are applied before entering the viewport. onEntertriggers the GSAP animation.- Optional
onLeaveruns when exiting. - Repeat logic is handled internally.
🧪 Next.js Support
Fully compatible with:
- App Router
- Server Components
- Client Components
- SSR
Only animation components run client-side.
RevealObserverSetup is optional and only required if you need custom observer configuration.
🧩 Architecture
The library is built around:
createRevealSystem()→ animation system factoryReveal→ scroll-triggered animation componentRevealDefaultsProvider→ optional runtime defaultsRevealObserverSetup→ optional singleton observer configurationInView→ polymorphic component that detects when its children enter or leave the viewport
This ensures:
- No unnecessary observers
- Fully isolated animation systems
- Predictable behavior
- Clean TypeScript inference
- Minimal abstraction over GSAP
🎯 Philosophy
This library focuses on:
- Simplicity
- Type safety
- Modern React patterns
- Performance via a singleton observer
- Predictable scroll-based animations
It does not aim to replace GSAP ScrollTrigger — it provides a lighter alternative when full timeline orchestration is not required.
📄 License
ISC
