Skip to main content

Scroll Bloom Image

A scroll-driven image bloom: as the stage scrolls into view the container widens from a narrow strip to full width while the overscanned image zooms out toward 1x and the corners round in the final stretch. All three channels (width, scale, radius) are smoothed through springs so fast scrolling never snaps.

Micro InteractionReactMotionTailwind CSS
CSSTailwind

Manual

Create a file and paste the following code into it.

scroll-bloom-image.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
"use client";

import { type RefObject, useRef } from "react";
import { motion, useScroll, useSpring, useTransform } from "motion/react";

/* ------------------------------------------------------------------ */
/*  ScrollBloomImage                                                   */
/* ------------------------------------------------------------------ */

interface ScrollBloomImageProps {
  src: string;
  alt: string;
  /** Stage height [Optional, default: "72vh"] */
  height?: string;
  /** Container width at rest / fully bloomed [Optional] */
  fromWidth?: string;
  toWidth?: string;
  /** Corner radius once the radiusStart threshold passes [Optional] */
  fromRadius?: string;
  toRadius?: string;
  /** Scroll progress (0-1) where the radius begins animating [Optional, default: 0.5] */
  radiusStart?: number;
  /** Inner image overscan + zoom-out range [Optional] */
  innerWidth?: string;
  fromScale?: number;
  toScale?: number;
  stiffness?: number;
  damping?: number;
  /** Scrollable ancestor (defaults to the viewport) [Optional] */
  container?: RefObject<HTMLElement | null>;
  className?: string;
}

/**
 * A scroll-driven image bloom: as the stage scrolls into view the container
 * widens from a narrow strip to full width while the overscanned image
 * zooms out toward 1x and the corners round in the final stretch — all three
 * channels smoothed through springs so fast scrolling never snaps.
 * @param {string} src - Image source [Required]
 * @param {RefObject} container - Scrollable ancestor ref [Optional, default: viewport]
 */
export function ScrollBloomImage({
  src,
  alt,
  height = "72vh",
  fromWidth = "42%",
  toWidth = "96%",
  fromRadius = "0px",
  toRadius = "24px",
  radiusStart = 0.5,
  innerWidth = "96vw",
  fromScale = 1.6,
  toScale = 1,
  stiffness = 120,
  damping = 80,
  container,
  className,
}: ScrollBloomImageProps) {
  const stageRef = useRef<HTMLDivElement | null>(null);
  const { scrollYProgress } = useScroll({
    target: stageRef,
    container,
    offset: ["start end", "start start"],
  });

  const width = useTransform(scrollYProgress, [0, 1], [fromWidth, toWidth]);
  const scale = useTransform(scrollYProgress, [0, 1], [fromScale, toScale]);
  const radius = useTransform(scrollYProgress, [radiusStart, 1], [fromRadius, toRadius]);

  const smoothWidth = useSpring(width, { stiffness, damping });
  const smoothScale = useSpring(scale, { stiffness, damping });
  const smoothRadius = useSpring(radius, { stiffness, damping });

  return (
    <motion.div
      ref={stageRef}
      className={className}
      style={{
        width: smoothWidth,
        position: "relative",
        height,
        borderRadius: smoothRadius,
        overflow: "hidden",
        margin: "0 auto",
      }}
    >
      <motion.div
        style={{
          position: "absolute",
          left: "50%",
          x: "-50%",
          width: innerWidth,
          height: "100%",
          scale: smoothScale,
          originX: 0.5,
          originY: 0.5,
        }}
      >
        <img src={src} alt={alt} className="h-full w-full object-cover" loading="lazy" decoding="async" />
      </motion.div>
    </motion.div>
  );
}

Update the import paths to match your project setup.

Similar components

Tilt Pointer

Halftone Portrait

Edge Veil Blur

Spotlight Follow

Install via CLI

Resource details

PublishedJuly 17, 2026
CategoryMicro Interaction
ReactMotionTailwind CSS