Cambio Image

PreviousNext

A molecular image component featuring physics-based zoom transitions, blur-up loading, and gesture-driven dismissal

This component is based on Cambio Image by Raphael Salaja.

Installation

pnpm dlx shadcn@latest add https://deltacomponents.dev/r/cambio-image.json
Required CSS

To ensure the zoomed image sits above all other UI elements (like sticky navbars), add this style to your globals.css. This handles the z-index elevation when the component enters the open state.

.root {
  isolation: isolate;
}
 
[data-state="open"] {
  z-index: 999 !important;
}

Usage

The component mirrors the standard HTML <img> API but requires explicit dimensions to calculate the zoom physics correctly.

import { CambioImage } from "@/components/ui/cambio-image"
 
export default function Example() {
  return (
    <CambioImage
      src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800&h=600&fit=crop"
      alt="Beautiful mountain landscape"
      width={800}
      height={600}
      motion="smooth"
      className="rounded-lg"
    />
  )
}

Examples

When rendering images in a grid/masonry layout, the browser's stacking context can cause overlapping issues during the closing animation.

Pass the index prop (usually from your map function) to ensure the active image always animates above its neighbors.

<div className="grid grid-cols-3 gap-4">
  {images.map((img, i) => (
    <CambioImage
      key={img.src}
      src={img.src}
      index={i} // Critical for correct layering
      {...img}
    />
  ))}
</div>

Dismiss on Scroll

For a more fluid mobile experience, you can enable dismissOnScroll. This mimics native mobile gallery behavior where scrolling away closes the image.

<CambioImage
  src="..."
  dismissOnScroll={true}
  dismissOnImageClick={true} // Optional: click image to close
/>
Scroll Context

The dismissOnScroll behavior may not function as expected when the component is rendered within constrained scroll containers such as demo previews or tab panels used above. For the best experience testing this functionality, view the full-screen demo where scroll events propagate correctly.

Motion Presets

You can customize the animation physics using the motion prop. See the Motion Presets table in the API Reference for all available options.

<div className="flex gap-4">
  {/* Standard snappy feel */}
  <CambioImage src="/img-1.jpg" motion="snappy" width={400} height={300} />
 
  {/* Bouncy, playful feel */}
  <CambioImage src="/img-2.jpg" motion="bouncy" width={400} height={300} />
</div>

Notes

Disabling Initial Blur

The component uses an IntersectionObserver to trigger a blur-to-focus animation when the image enters the viewport. For images "above the fold" (LCP candidates), you should disable this to prevent layout shifts or visual delays.

<CambioImage src="/hero.jpg" enableInitialAnimation={false} loading="eager" />

Advanced Motion Config

For granular control, you can pass an object to the motion prop to configure specific phases of the animation independently:

<CambioImage
  motion={{
    trigger: "smooth", // The image expanding
    backdrop: "reduced", // The background fade
    popup: "bouncy", // The final resting state
  }}
/>

API Reference

Props

PropTypeDefaultDescription
srcstring-Required. The source URL.
altstring-Required. Accessibility text.
widthnumber-Required. Intrinsic width.
heightnumber-Required. Intrinsic height.
motionPreset | Config"snappy"Motion preset or custom config.
loading"lazy" | "eager""lazy"Image loading strategy.
indexnumber0Z-index offset for list rendering.
dismissOnScrollbooleanfalseClose the modal on window scroll.
dismissOnImageClickbooleanfalseClose by clicking the zoomed image itself.
enableInitialAnimationbooleantrueBlur-to-focus effect on viewport entry.
draggablebooleanfalseAllows the image to be dragged (ghost image).

Motion Presets

PresetDescription
snappyFast, linear transition. (Default)
smoothEased, slower transition.
bouncySpring-physics based animation.
reducedMinimal motion for accessibility.