Velocity UI
Loading…
Menu

Sticky Scroll Features

Sticky Scroll Features component with smooth animations and modern UI.

Installation

Add this component to your project using the CLI:

terminal
npx vui-registry-cli-v1 add sticky-scroll-features

Source Code

sticky-scroll-features.tsx
"use client"

import React, { useEffect, useRef, useState } from "react"
import { motion, useScroll, useMotionValueEvent } from "framer-motion"
import { cn } from "@/lib/utils"
import { Layout, GitBranch, Terminal, Globe, Zap, Layers, Code2, Cpu } from "lucide-react"

const content = [
  {
    title: "Collaborative Editing",
    description:
      "Work together in real-time with your team, clients, and stakeholders. Collaborate on every aspect of your project, from design to deployment.",
    content: (
      <div className="h-full w-full bg-[linear-gradient(to_bottom_right,var(--cyan-500),var(--emerald-500))] flex items-center justify-center text-white">
        <Layout className="h-10 w-10" />
      </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.",
    content: (
      <div className="h-full w-full  flex items-center justify-center text-white">
        <GitBranch className="h-10 w-10" />
      </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.",
    content: (
      <div className="h-full w-full bg-[linear-gradient(to_bottom_right,var(--orange-500),var(--yellow-500))] flex items-center justify-center text-white">
        <Terminal className="h-10 w-10" />
      </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.",
    content: (
      <div className="h-full w-full bg-[linear-gradient(to_bottom_right,var(--cyan-500),var(--emerald-500))] flex items-center justify-center text-white">
        <Globe className="h-10 w-10" />
      </div>
    ),
  },
]

export default function StickyScrollFeatures() {
  const [activeCard, setActiveCard] = useState(0)
  const ref = useRef<HTMLDivElement>(null)
  const { scrollYProgress } = useScroll({
    // 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 closest = cardsBreakpoints.reduce((prev, curr) => {
      return Math.abs(curr - latest) < Math.abs(prev - latest) ? curr : prev
    })
    const index = cardsBreakpoints.indexOf(closest)
    setActiveCard(index)
  })

  const backgroundColors = [
    "bg-slate-900",
    "bg-black",
    "bg-neutral-900",
  ]
  const linearGradients = [
    "linear-gradient(to bottom right, var(--cyan-500), var(--emerald-500))",
    "linear-gradient(to bottom right, var(--pink-500), var(--indigo-500))",
    "linear-gradient(to bottom right, var(--orange-500), var(--yellow-500))",
    "linear-gradient(to bottom right, var(--red-500), var(--orange-500))",
  ]

  return (
    <motion.div
      animate={{
        backgroundColor: backgroundColors[activeCard % backgroundColors.length],
      }}
      className="h-[30rem] w-full overflow-y-auto flex justify-center relative space-x-10 rounded-3xl p-10 scrollbar-hide font-sans transition-colors duration-500"
      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 text-slate-300 max-w-sm mt-10"
              >
                {item.description}
              </motion.p>
            </div>
          ))}
          <div className="h-40" />
        </div>
      </div>
      <motion.div
        animate={{
          background: linearGradients[activeCard % linearGradients.length],
        }}
        className={cn(
          "hidden lg:block h-60 w-80 rounded-2xl bg-white sticky top-10 overflow-hidden",
          content[activeCard].content ?? "bg-transparent"
        )}
      >
        {content[activeCard].content}
      </motion.div>
    </motion.div>
  )
}