Skip to content

Scroll-Linked Image Mask Reveal

An image whose circular clip-path mask grows and shrinks in lockstep with scroll.

Freeeditorialminimal
Category
Sections
Added
2026-08-24
Deps
1
Updated
2026-09-01
Preview
scroll ↓
scroll ↑

Props

This entry takes no configurable props.

Requires framer-motion
"use client";

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

export function ScrollLinkedImageMaskReveal({ src, alt = "" }: { src: string; alt?: string }) {
  const targetRef = useRef<HTMLDivElement>(null);

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

  const clipPercent = useTransform(scrollYProgress, [0, 1], [0, 75]);
  const clipPath = useTransform(clipPercent, (v) => `circle(${v}% at 50% 50%)`);
  const scale = useTransform(scrollYProgress, [0, 1], [1.3, 1]);

  return (
    <div ref={targetRef} className="relative flex h-[70vh] items-center justify-center overflow-hidden">
      <motion.img src={src} alt={alt} style={{ clipPath, scale }} className="h-full w-full object-cover" />
    </div>
  );
}
Notes

About this section

Most reveal-on-scroll components fire once when an element crosses some threshold and then sit in their finished state, which is fine until someone scrolls back up and the reveal stays frozen, disconnected from where they actually are on the page. This one does not fire in that sense at all: a circular clip-path radius is derived directly from scrollYProgress via useTransform, so the mask grows and shrinks continuously and bidirectionally as the container passes through the viewport, tracked with useScroll's target and offset pair rather than a one-shot useInView check. Because clip-path and scale are motion values rather than React state, they update every scroll frame without triggering a re-render, which is what keeps this smooth even stacked several to a page. Reach for the sibling component, Scroll Reveal Wipe, instead when the reveal should commit once and stay revealed; that one is a useInView, single-fire animation, and mixing the two mental models, continuous versus one-shot, on the same page can look inconsistent if you are not deliberate about which effect is doing which job.

Curator’s note

Unlike Scroll Reveal Wipe (a once-triggered useInView animation), this mask is driven directly by a continuous scrollYProgress motion value via useTransform — it tracks scroll bidirectionally in real time rather than firing once and staying in its end state.

Related

Pairs well with

Matched on shared tags and category — the entries most likely to be used alongside this one.

A scroll-triggered vertical timeline whose dots and cards reveal in sequence.

timelinescrollreveal
Pro

Reveals text one line at a time, each sliding up from behind its own clipping mask.

text-revealmaskscroll
Pro

A wrapper that reveals its children one after another as the group scrolls into view.

staggerrevealscroll