scroll·free
Scroll Reveal
Fade-and-slide elements into view on scroll, with optional stagger and blur.
Fast
Scroll into view to reveal each card in sequence.
Composable
Scroll into view to reveal each card in sequence.
Typed
Scroll into view to reveal each card in sequence.
Installation
terminal
pnpm add gsap @gsap/reactThen copy the component below into src/components/motion/scroll-reveal.tsx. It expects @/lib/gsap (plugin registration) and @/lib/utils (cn).
Source
src/components/motion/scroll-reveal.tsx
"use client";
import { useRef, type ReactNode } from "react";
import { gsap, useGSAP } from "@/lib/gsap";
import { cn } from "@/lib/utils";
type Direction = "up" | "down" | "left" | "right" | "none";
export interface ScrollRevealProps {
children: ReactNode;
className?: string;
/** Direction the element travels from. */
from?: Direction;
/** Travel distance in px. */
distance?: number;
duration?: number;
delay?: number;
/** Stagger direct children instead of animating the wrapper. */
stagger?: number;
/** ScrollTrigger start position, e.g. "top 80%". */
start?: string;
once?: boolean;
blur?: boolean;
}
const offset = (from: Direction, d: number) => {
switch (from) {
case "up":
return { y: d };
case "down":
return { y: -d };
case "left":
return { x: d };
case "right":
return { x: -d };
default:
return {};
}
};
export function ScrollReveal({
children,
className,
from = "up",
distance = 40,
duration = 0.9,
delay = 0,
stagger = 0,
start = "top 85%",
once = true,
blur = false,
}: ScrollRevealProps) {
const ref = useRef<HTMLDivElement>(null);
useGSAP(
() => {
const el = ref.current;
if (!el) return;
const targets = stagger > 0 ? Array.from(el.children) : el;
gsap.fromTo(
targets,
{ opacity: 0, filter: blur ? "blur(12px)" : "none", ...offset(from, distance) },
{
opacity: 1,
x: 0,
y: 0,
filter: "blur(0px)",
duration,
delay,
stagger,
ease: "power3.out",
scrollTrigger: {
trigger: el,
start,
toggleActions: once ? "play none none none" : "play none none reverse",
},
}
);
},
{ scope: ref, dependencies: [from, distance, duration, delay, stagger, start, once, blur] }
);
return (
<div ref={ref} className={cn(className)}>
{children}
</div>
);
}