April 8, 2026/Intermediate/Scroll & Parallax

Zoom Parallax

Five images sit in a sticky viewport. As you scroll through 300vh, each image scales up at a different rate. The center image scales 1→4 to fill the screen; outer images scale faster (up to 1→9), flying past the frame.

"use client";
import { useRef } from "react";
import Image from "next/image";
import { motion, useScroll, useTransform } from "framer-motion";
import styles from "./styles.module.css";

// Each image element needs its own hook call — extracted to sub-component
function ZoomImage({ src, progress, scaleRange, offsetX, offsetY }) {
  const scale = useTransform(progress, [0, 1], scaleRange);
  return (
    <div className={styles.elWrapper} style={{ marginLeft: offsetX, marginTop: offsetY }}>
      <motion.div className={styles.el} style={{ scale }}>
        <div className={styles.imageContainer}>
          <Image src={src} alt="" fill className={styles.img} sizes="25vw" />
        </div>
      </motion.div>
    </div>
  );
}

// 5 images: scale ranges + offsets from sticky center
const PICTURES = [
  { scaleRange: [1, 4], offsetX: "0",        offsetY: "0" },
  { scaleRange: [1, 5], offsetX: "-27.5vw",  offsetY: "-25vh" },
  { scaleRange: [1, 6], offsetX: "27.5vw",   offsetY: "-30vh" },
  { scaleRange: [1, 8], offsetX: "-22.5vw",  offsetY: "20vh"  },
  { scaleRange: [1, 9], offsetX: "25vw",     offsetY: "15vh"  },
];

export default function ZoomParallax({ images = [] }) {
  const containerRef = useRef(null);

  const { scrollYProgress } = useScroll({
    target: containerRef,
    offset: ["start start", "end end"],
  });

  const srcs = images.length >= 5 ? images : [...images, ...images, ...images].slice(0, 5);

  return (
    <div ref={containerRef} className={styles.container}>
      <div className={styles.sticky}>
        {PICTURES.map((pic, i) => (
          <ZoomImage
            key={i}
            src={srcs[i]}
            progress={scrollYProgress}
            scaleRange={pic.scaleRange}
            offsetX={pic.offsetX}
            offsetY={pic.offsetY}
          />
        ))}
      </div>
    </div>
  );
}
  • framer-motion

Related Components

NEWTODAY
Exo Ape Play Reelscroll-parallax
NEW5D AGO
Garoa Video Scrollscroll-parallax
NEW5D AGO
Relai Parallax Scroll Heroscroll-parallax