All components
Sticky Scroll Reveal
effectsAceternity UI component.
responsive · 620px
Install
Same command in any shadcn project — React (Vite/CRA), Next.js, Remix, Astro, and more:
$
npx shadcn@latest add https://your-domain/r/sticky-scroll-reveal.jsonUsage
"use client";
import { StickyScroll } from "@/registry/aceternity-ui/sticky-scroll-reveal";
const content = [
{
title: "Collaborative Editing",
description:
"Work together in real time with your team, clients, and stakeholders. Collaborate on documents, share ideas, and make decisions quickly. With our platform, you can streamline your workflow and increase productivity.",
content: (
<div className="flex h-full w-full items-center justify-center text-white text-lg font-semibold">
Collaborative Editing
</div>
),
},
{
title: "Real time changes",
description:
"See changes as they happen. With our platform, you can track every modification in real time. No more confusion about the latest version of your project. Say goodbye to the chaos of version control and embrace the simplicity of real-time updates.",
content: (
<div className="flex h-full w-full items-center justify-center text-white text-lg font-semibold">
Real Time Changes
</div>
),
},
{
title: "Version control",
description:
"Experience real-time updates and never stress about version control again. Our platform ensures that you're always working on the most recent version of your project, eliminating the need for constant manual updates.",
content: (
<div className="flex h-full w-full items-center justify-center text-white text-lg font-semibold">
Version Control
</div>
),
},
{
title: "Running out of content",
description:
"Experience real-time updates and never stress about version control again. Our platform ensures that you're always working on the most recent version of your project, eliminating the need for constant manual updates.",
content: (
<div className="flex h-full w-full items-center justify-center text-white text-lg font-semibold">
Running out of content
</div>
),
},
];
export default function Demo() {
return (
<div className="w-full max-w-5xl">
<StickyScroll content={content} />
</div>
);
}Component source
"use client";
import React, { useEffect, useRef, useState } from "react";
import { useMotionValueEvent, useScroll } from "motion/react";
import { motion } from "motion/react";
import { cn } from "@/lib/utils";
export const StickyScroll = ({
content,
contentClassName,
}: {
content: {
title: string;
description: string;
content?: React.ReactNode | any;
}[];
contentClassName?: string;
}) => {
const [activeCard, setActiveCard] = React.useState(0);
const ref = useRef<any>(null);
const { scrollYProgress } = useScroll({
// uncomment line 22 and comment line 23 if you DONT want the overflow container and want to have it change on the entire page scroll
// target: ref
container: ref,
offset: ["start start", "end start"],
});
const cardLength = content.length;
useMotionValueEvent(scrollYProgress, "change", (latest) => {
const cardsBreakpoints = content.map((_, index) => index / cardLength);
const closestBreakpointIndex = cardsBreakpoints.reduce(
(acc, breakpoint, index) => {
const distance = Math.abs(latest - breakpoint);
if (distance < Math.abs(latest - cardsBreakpoints[acc])) {
return index;
}
return acc;
},
0,
);
setActiveCard(closestBreakpointIndex);
});
const backgroundColors = [
"#0f172a", // slate-900
"#000000", // black
"#171717", // neutral-900
];
const linearGradients = [
"linear-gradient(to bottom right, #06b6d4, #10b981)", // cyan-500 to emerald-500
"linear-gradient(to bottom right, #ec4899, #6366f1)", // pink-500 to indigo-500
"linear-gradient(to bottom right, #f97316, #eab308)", // orange-500 to yellow-500
];
const [backgroundGradient, setBackgroundGradient] = useState(
linearGradients[0],
);
useEffect(() => {
setBackgroundGradient(linearGradients[activeCard % linearGradients.length]);
}, [activeCard]);
return (
<motion.div
animate={{
backgroundColor: backgroundColors[activeCard % backgroundColors.length],
}}
className="relative flex h-[30rem] justify-center space-x-10 overflow-y-auto rounded-md p-10"
ref={ref}
>
<div className="div relative flex items-start px-4">
<div className="max-w-2xl">
{content.map((item, index) => (
<div key={item.title + index} className="my-20">
<motion.h2
initial={{
opacity: 0,
}}
animate={{
opacity: activeCard === index ? 1 : 0.3,
}}
className="text-2xl font-bold text-slate-100"
>
{item.title}
</motion.h2>
<motion.p
initial={{
opacity: 0,
}}
animate={{
opacity: activeCard === index ? 1 : 0.3,
}}
className="text-kg mt-10 max-w-sm text-slate-300"
>
{item.description}
</motion.p>
</div>
))}
<div className="h-40" />
</div>
</div>
<div
style={{ background: backgroundGradient }}
className={cn(
"sticky top-10 hidden h-60 w-80 overflow-hidden rounded-md bg-white lg:block",
contentClassName,
)}
>
{content[activeCard].content ?? null}
</div>
</motion.div>
);
};Dependencies
motion
Source: Aceternity UI