Docs
Sticky Scroll Reveal
A sticky container that sticks while scrolling, text reveals on scroll
Installation
Install dependencies
npm install framer-motion lucide-react
Update tailwind.config.js
Add the following to your tailwind.config.js file.
module.exports = {
theme: {
extend: {
}
}
}
Run the following command
It will create a new file sticky-scroll-reveal.tsx
inside the components/mage-ui/hero
directory.
mkdir -p components/mage-ui/hero && touch components/mage-ui/hero/sticky-scroll-reveal.tsx
Paste the code
Open the newly created file and paste the following code:
"use client";
import React, { useEffect, useRef, useState } from "react";
import { useMotionValueEvent, useScroll } from "framer-motion";
import { motion } from "framer-motion";
import { cn } from "@/lib/utils";
import Image from "next/image";
interface ContentItem {
title: string;
description: string;
content?: React.ReactNode;
}
interface StickyScrollProps {
content: ContentItem[];
contentClassName?: string;
}
export const StickyScroll: React.FC<StickyScrollProps> = ({
content,
contentClassName,
}) => {
const [activeCard, setActiveCard] = useState(0);
const ref = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({
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", "#000000", "#171717"];
const linearGradients = [
"linear-gradient(to bottom right, #06b6d4, #10b981)",
"linear-gradient(to bottom right, #ec4899, #6366f1)",
"linear-gradient(to bottom right, #f97316, #eab308)",
];
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] w-full flex-col md:flex-row justify-center space-y-6 md:space-y-0 md:space-x-10 overflow-y-auto rounded-md p-6 md:p-10"
ref={ref}
>
<div className="relative flex items-start px-2 md:px-4 w-full md:w-1/2">
<div className="max-w-4xl w-full">
{content.map((item, index) => (
<div key={item.title + index} className="my-16 md:my-20">
<motion.h2
initial={{ opacity: 0 }}
animate={{ opacity: activeCard === index ? 1 : 0.3 }}
className="text-xl md: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-sm md:text-base mt-4 md:mt-10 max-w-lg text-slate-300"
>
{item.description}
</motion.p>
</div>
))}
<div className="h-40" />
</div>
</div>
<div
style={{ background: backgroundGradient }}
className={cn(
"sticky top-10 h-60 w-full md:w-96 overflow-hidden rounded-md",
contentClassName
)}
>
{content[activeCard].content ?? null}
</div>
</motion.div>
);
};
const content: ContentItem[] = [
{
title: "Collaborative Editing",
description:
"Work together in real time with your team, clients, and stakeholders.",
content: (
<div className="flex h-full w-full items-center justify-center text-white bg-gradient-to-br from-cyan-500 to-emerald-500">
Collaborative Editing
</div>
),
},
{
title: "Real time changes",
description: "See changes as they happen.",
content: (
<div className="flex h-full w-full items-center justify-center text-white">
<Image
src="/linear.webp"
width={300}
height={300}
className="h-full w-full object-cover"
alt="linear board demo"
/>
</div>
),
},
{
title: "Version control",
description: "Experience real-time updates and never stress about version control again.",
content: (
<div className="flex h-full w-full items-center justify-center text-white bg-gradient-to-br from-orange-500 to-yellow-500">
Version control
</div>
),
},
{
title: "Running out of content",
description: "Stay in the loop, keep your team aligned, and maintain the flow of your work.",
content: (
<div className="flex h-full w-full items-center justify-center text-white bg-gradient-to-br from-cyan-500 to-emerald-500">
Running out of content
</div>
),
},
];
const StickyScrollRevealDemo: React.FC = () => {
return (
<div className="w-full py-4">
<StickyScroll content={content} />
</div>
);
};
export default StickyScrollRevealDemo;