Web InnoventixFreeCode

Scale Pin Scroll Effect

Original · free

A pinned image scales up as you scroll past it.

byWeb InnoventixReact + Tailwind
scalepinscrollimageeffect
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/imgfx-scale-pin-scroll.json
imgfx-scale-pin-scroll.tsx
"use client";

import { useRef } from "react";
import { motion, useScroll, useTransform, useReducedMotion } from "motion/react";

export default function ImgfxScalePinScroll() {
  const reduce = useReducedMotion();
  const trackRef = useRef<HTMLDivElement>(null);

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

  const scale = useTransform(scrollYProgress, [0, 1], reduce ? [1, 1] : [1, 1.35]);
  const radius = useTransform(scrollYProgress, [0, 1], reduce ? [24, 24] : [24, 4]);
  const overlay = useTransform(scrollYProgress, [0, 0.6, 1], [0.45, 0.15, 0.05]);
  const captionY = useTransform(scrollYProgress, [0, 1], reduce ? [0, 0] : [24, -8]);
  const captionOpacity = useTransform(scrollYProgress, [0, 0.25, 1], [0, 1, 1]);

  return (
    <section className="relative w-full bg-neutral-50 dark:bg-neutral-950">
      <style>{`
        @keyframes spsScroll_bob {
          0%, 100% { transform: translateY(0); opacity: 0.9; }
          50% { transform: translateY(6px); opacity: 0.4; }
        }
        @media (prefers-reduced-motion: reduce) {
          .spsScroll_hint { animation: none !important; }
        }
      `}</style>

      {/* Intro copy */}
      <div className="mx-auto max-w-3xl px-6 pt-24 pb-12 text-center sm:pt-32">
        <p className="text-xs font-semibold uppercase tracking-[0.25em] text-indigo-600 dark:text-indigo-400">
          Scroll Effect
        </p>
        <h2 className="mt-4 text-3xl font-semibold tracking-tight text-neutral-900 dark:text-neutral-50 sm:text-4xl">
          Pin &amp; scale on scroll
        </h2>
        <p className="mt-4 text-base leading-relaxed text-neutral-600 dark:text-neutral-400">
          The image locks to the viewport and grows as you keep scrolling &mdash; a cinematic
          push-in that pulls the eye straight into the frame.
        </p>
      </div>

      {/* Scroll track: tall wrapper drives the pinned scale */}
      <div ref={trackRef} className="relative h-[220vh]">
        <div className="sticky top-0 flex h-screen items-center justify-center overflow-hidden px-4 sm:px-8">
          <motion.figure
            style={{ scale, borderRadius: radius }}
            className="relative h-[62vh] w-full max-w-5xl overflow-hidden bg-neutral-200 shadow-2xl shadow-neutral-900/20 ring-1 ring-black/5 dark:bg-neutral-800 dark:shadow-black/50 dark:ring-white/10 sm:h-[68vh]"
          >
            {/* eslint-disable-next-line @next/next/no-img-element */}
            <img
              src="/img/gallery/g14.webp"
              alt="Aerial view of a winding coastal road meeting the sea at dusk"
              loading="lazy"
              draggable={false}
              className="absolute inset-0 h-full w-full object-cover"
            />

            <motion.div
              style={{ opacity: overlay }}
              className="pointer-events-none absolute inset-0 bg-gradient-to-t from-neutral-950 via-neutral-950/20 to-transparent"
            />

            <motion.figcaption
              style={{ y: captionY, opacity: captionOpacity }}
              className="absolute inset-x-0 bottom-0 p-6 sm:p-10"
            >
              <span className="inline-flex items-center rounded-full bg-white/90 px-3 py-1 text-[11px] font-semibold uppercase tracking-wider text-neutral-900 backdrop-blur dark:bg-neutral-900/80 dark:text-neutral-100">
                Field notes
              </span>
              <p className="mt-3 max-w-md text-lg font-medium leading-snug text-white drop-shadow-sm sm:text-xl">
                Keep scrolling &mdash; the frame draws you closer with every pixel.
              </p>
            </motion.figcaption>
          </motion.figure>
        </div>
      </div>

      {/* Hint */}
      <div className="mx-auto max-w-3xl px-6 pb-24 text-center">
        <p className="spsScroll_hint text-sm text-neutral-500 dark:text-neutral-500" style={{ animation: "spsScroll_bob 1.8s ease-in-out infinite" }}>
          Scroll through the section to drive the zoom.
        </p>
      </div>
    </section>
  );
}

Dependencies

motion

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →