Zoom Scroll

Content scales from far to close as you scroll — Apple AirPods product page effect with scale and blur interpolation

scrollzoomscaleappleframer-motion
Install dependencies
$npm install framer-motion
Preview

Source Code

"use client";

import { ReactNode, useRef } from "react";
import { motion, useScroll, useTransform, MotionValue } from "framer-motion";

interface ZoomScrollProps {
  children: ReactNode;
  fromScale?: number;
  toScale?: number;
  fromBlur?: number;
  toBlur?: number;
  className?: string;
}

export default function ZoomScroll({
  children,
  fromScale = 0.35,
  toScale = 1,
  fromBlur = 10,
  toBlur = 0,
  className = "",
}: ZoomScrollProps) {
  const ref = useRef<HTMLDivElement>(null);

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

  const scale = useTransform(
    scrollYProgress,
    [0, 0.5, 1],
    [fromScale, toScale, toScale],
  );

  const blur: MotionValue<string> = useTransform(
    scrollYProgress,
    [0, 0.5, 1],
    [`blur(${fromBlur}px)`, `blur(${toBlur}px)`, `blur(${toBlur}px)`],
  );

  const opacity = useTransform(
    scrollYProgress,
    [0, 0.2, 0.5, 1],
    [0.25, 0.7, 1, 1],
  );

  return (
    <div
      ref={ref}
      className={`relative h-[200vh] w-full ${className}`}
    >
      <div className="sticky top-0 flex h-screen w-full items-center justify-center overflow-hidden">
        <motion.div
          style={{
            scale,
            filter: blur,
            opacity,
            transformOrigin: "center center",
            willChange: "transform, filter",
          }}
          className="flex h-full w-full items-center justify-center"
        >
          {children}
        </motion.div>
      </div>
    </div>
  );
}