Docs

Hero Parallax

A scroll effect with rotation, translation and opacity animations.

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 hero-parallax.tsx inside the components/mage-ui/background directory.

mkdir -p components/mage-ui/background && touch components/mage-ui/background/hero-parallax.tsx

Paste the code

Open the newly created file and paste the following code:

"use client";
import React from "react";
import {
  motion,
  useScroll,
  useTransform,
  useSpring,
  MotionValue,
} from "framer-motion";
import Image from "next/image";
import Link from "next/link";
 
export default function HeroParallaxPage() {
  return <HeroParallax products={products} />;
}
 
export const HeroParallax = ({
  products,
}: {
  products: {
    title: string;
    link: string;
    thumbnail: string;
  }[];
}) => {
  // Properly distribute products across the three rows
  const firstRow = products.slice(0, Math.ceil(products.length / 3));
  const secondRow = products.slice(Math.ceil(products.length / 3), Math.ceil(products.length / 3) * 2);
  const thirdRow = products.slice(Math.ceil(products.length / 3) * 2);
 
  const ref = React.useRef(null);
  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ["start start", "end start"],
  });
 
  // Enhanced spring configuration for ultra-smooth animations
  const ultraSmoothSpringConfig = {
    stiffness: 100,  // Reduced from 300 for smoother motion
    damping: 50,     // Increased from 30 for less oscillation
    bounce: 0,       // Removed bounce for more professional movement
    mass: 1.2,       // Added mass for more inertia and natural feel
    restDelta: 0.001 // Small threshold for stopping the animation
  };
 
  const translateX = useSpring(
    useTransform(scrollYProgress, [0, 1], [0, 800]),
    ultraSmoothSpringConfig
  );
  const translateXReverse = useSpring(
    useTransform(scrollYProgress, [0, 1], [0, -800]),
    ultraSmoothSpringConfig
  );
  const rotateX = useSpring(
    useTransform(scrollYProgress, [0, 0.2], [10, 0]),
    ultraSmoothSpringConfig
  );
  const opacity = useSpring(
    useTransform(scrollYProgress, [0, 0.2], [0.2, 1]),
    { ...ultraSmoothSpringConfig, stiffness: 80 } // Slower opacity changes feel smoother
  );
  const rotateZ = useSpring(
    useTransform(scrollYProgress, [0, 0.2], [15, 0]),
    ultraSmoothSpringConfig
  );
  const translateY = useSpring(
    useTransform(scrollYProgress, [0, 0.3], [-500, 300]), // Adjusted for smoother vertical movement
    ultraSmoothSpringConfig
  );
 
  return (
    <div
      ref={ref}
      className="h-[620vh] py-40 overflow-hidden antialiased relative flex flex-col self-auto [perspective:1000px] [transform-style:preserve-3d]"
    >
      <Header />
      <motion.div
        style={{
          rotateX,
          rotateZ,
          translateY,
          opacity,
        }}
        className="container mx-auto"
      >
        <motion.div className="flex flex-row-reverse space-x-reverse space-x-20 mb-20 justify-center">
          {firstRow.map((product) => (
            <ProductCard
              product={product}
              translate={translateX}
              key={product.title + product.link}
            />
          ))}
        </motion.div>
        <motion.div className="flex flex-row mb-20 space-x-20 justify-center">
          {secondRow.map((product) => (
            <ProductCard
              product={product}
              translate={translateXReverse}
              key={product.title + product.link}
            />
          ))}
        </motion.div>
        <motion.div className="flex flex-row-reverse space-x-reverse space-x-20 justify-center">
          {thirdRow.map((product) => (
            <ProductCard
              product={product}
              translate={translateX}
              key={product.title + product.link}
            />
          ))}
        </motion.div>
      </motion.div>
    </div>
  );
};
 
export const Header = () => {
  return (
    <div className="max-w-7xl relative mx-auto py-20 md:py-40 px-4 w-full left-0 top-0">
      <h1 className="text-2xl md:text-7xl font-bold dark:text-white">
        The Ultimate <br /> development studio
      </h1>
      <p className="max-w-2xl text-base md:text-xl mt-8 dark:text-neutral-200">
        We build beautiful products with the latest technologies and frameworks.
        We are a team of passionate developers and designers that love to build
        amazing products.
      </p>
    </div>
  );
};
 
export const ProductCard = ({
  product,
  translate,
}: {
  product: {
    title: string;
    link: string;
    thumbnail: string;
  };
  translate: MotionValue<number>;
}) => {
  // Enhanced hover animation for ultra-smooth card movement
  const hoverTransition = {
    type: "spring",
    stiffness: 200,
    damping: 20,
    mass: 1.1
  };
 
  return (
    <motion.div
      style={{ x: translate }}
      whileHover={{
        y: -20,
        scale: 1.02,
        transition: hoverTransition
      }}
      key={product.title}
      className="group/product h-96 w-[30rem] relative shrink-0"
    >
      <Link href={product.link} className="block group-hover/product:shadow-2xl">
        <Image
          src={product.thumbnail}
          height={600}
          width={600}
          className="object-cover object-left-top absolute h-full w-full inset-0"
          alt={product.title}
        />
      </Link>
      <motion.div
        className="absolute inset-0 h-full w-full opacity-0 group-hover/product:opacity-80 bg-black pointer-events-none"
        initial={{ opacity: 0 }}
        whileHover={{ opacity: 0.8 }}
        transition={{ duration: 0.3 }}
      />
      <motion.h2
        className="absolute bottom-4 left-4 opacity-0 group-hover/product:opacity-100 text-white"
        initial={{ opacity: 0, y: 10 }}
        whileHover={{ opacity: 1, y: 0 }}
        transition={{ duration: 0.3, delay: 0.1 }}
      >
        {product.title}
      </motion.h2>
    </motion.div>
  );
};
 
 
 
export const products = [
  { title: "Eiffel Tower", link: "https://www.toureiffel.paris/en", thumbnail: "https://upload.wikimedia.org/wikipedia/commons/a/a8/Tour_Eiffel_Wikimedia_Commons.jpg" },
  { title: "Colosseum", link: "https://www.coopculture.it/en/colosseo-e-shop.cfm", thumbnail: "https://upload.wikimedia.org/wikipedia/commons/d/de/Colosseo_2020.jpg" },
  { title: "Machu Picchu", link: "https://www.peru.travel/en/attractions/machu-picchu", thumbnail: "https://upload.wikimedia.org/wikipedia/commons/e/eb/Machu_Picchu%2C_Peru.jpg" },
  { title: "Colosseum", link: "https://www.coopculture.it/en/colosseo-e-shop.cfm", thumbnail: "https://upload.wikimedia.org/wikipedia/commons/d/de/Colosseo_2020.jpg" },
  { title: "Statue of Liberty", link: "https://www.nps.gov/stli/index.htm", thumbnail: "https://upload.wikimedia.org/wikipedia/commons/a/a1/Statue_of_Liberty_7.jpg" },
  { title: "Eiffel Tower", link: "https://www.toureiffel.paris/en", thumbnail: "https://upload.wikimedia.org/wikipedia/commons/a/a8/Tour_Eiffel_Wikimedia_Commons.jpg" },
  { title: "Statue of Liberty", link: "https://www.nps.gov/stli/index.htm", thumbnail: "https://upload.wikimedia.org/wikipedia/commons/a/a1/Statue_of_Liberty_7.jpg" },
  { title: "Machu Picchu", link: "https://www.peru.travel/en/attractions/machu-picchu", thumbnail: "https://upload.wikimedia.org/wikipedia/commons/e/eb/Machu_Picchu%2C_Peru.jpg" },
  { title: "Colosseum", link: "https://www.coopculture.it/en/colosseo-e-shop.cfm", thumbnail: "https://upload.wikimedia.org/wikipedia/commons/d/de/Colosseo_2020.jpg" },
  { title: "Machu Picchu", link: "https://www.peru.travel/en/attractions/machu-picchu", thumbnail: "https://upload.wikimedia.org/wikipedia/commons/e/eb/Machu_Picchu%2C_Peru.jpg" },
];